学说orderBy注释。根据关联的实体对实体关联进行排序。

时间:2014-01-10 02:00:28

标签: php symfony doctrine-orm

假设有以下实体与Symfony应用程序。

class List
{
    /**
     * @ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
     * @ORM\OrderBy({"category.title" = "ASC"})
     */
    protected $listItems;
}

class ListItem
{
    /**
     * @ORM\ManyToOne(targetEntity="List", inversedBy="listItems")
     */
    protected $list;

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="listItems")
     */
    protected $category;
}

class Category
{
    /**
     * @ORM\OneToMany(targetEntity="ListItem", mappedBy="cateogory")
     */
    protected $listItems;

    protected $title;
}

不幸的是,orderBy参数category.title在学说中不起作用。我的理解是,最常见的解决方案是在ListItem实体上存储额外的属性,例如$categoryTitle,并在orderBy注释中使用此新字段。例如;

class List
{
    /**
     * @ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
     * @ORM\OrderBy({"categoryTitle" = "ASC"})
     */
    protected $listItems;
}

class ListItem
{
    // --

    protected $categoryTitle
}

这种方法的问题是通过set方法和/或监听器保持最新$categoryTitle,以及显然是数据库数据的非规范化的额外开销。

我是否可以使用一种方法来订购与doctrine的关联,而不会降低数据库的质量?

1 个答案:

答案 0 :(得分:2)

为了解决这个问题,我将以下方法添加到我们所有实体扩展的abstractEntity,因此所有实体都可以对它们的集合进行排序。

以下代码尚未经过任何测试,但对于将来可能遇到此问题的任何人来说,它应该是一个很好的起点。

/**
 * This method will change the order of elements within a Collection based on the given method.
 * It preserves array keys to avoid any direct access issues but will order the elements
 * within the array so that iteration will be done in the requested order.
 *
 * @param string $property
 * @param array  $calledMethods
 *
 * @return $this
 * @throws \InvalidArgumentException
 */
public function orderCollection($property, $calledMethods = array())
{
    /** @var Collection $collection */
    $collection = $this->$property;

    // If we have a PersistentCollection, make sure it is initialized, then unwrap it so we
    // can edit the underlying ArrayCollection without firing the changed method on the
    // PersistentCollection. We're only going in and changing the order of the underlying ArrayCollection.
    if ($collection instanceOf PersistentCollection) {
        /** @var PersistentCollection $collection */
        if (false === $collection->isInitialized()) {
            $collection->initialize();
        }
        $collection = $collection->unwrap();
    }

    if (!$collection instanceOf ArrayCollection) {
        throw new InvalidArgumentException('First argument of orderCollection must reference a PersistentCollection|ArrayCollection within $this.');
    }

    $uaSortFunction = function($first, $second) use ($calledMethods) {

        // Loop through $calledMethods until we find a orderable difference
        foreach ($calledMethods as $callMethod => $order) {

            // If no order was set, swap k => v values and set ASC as default.
            if (false == in_array($order, array('ASC', 'DESC')) ) {
                $callMethod = $order;
                $order = 'ASC';
            }

            if (true == is_string($first->$callMethod())) {

                // String Compare
                $result = strcasecmp($first->$callMethod(), $second->$callMethod());

            } else {

                // Numeric Compare
                $difference = ($first->$callMethod() - $second->$callMethod());
                // This will convert non-zero $results to 1 or -1 or zero values to 0
                // i.e. -22/22 = -1; 0.4/0.4 = 1;
                $result = (0 != $difference) ? $difference / abs($difference): 0;
            }

            // 'Reverse' result if DESC given
            if ('DESC' == $order) {
                $result *= -1;
            }

            // If we have a result, return it, else continue looping
            if (0 !== (int) $result) {
                return (int) $result;
            }
        }

        // No result, return 0
        return 0;
    };

    // Get the values for the ArrayCollection and sort it using the function
    $values = $collection->getValues();
    uasort($values, $uaSortFunction);

    // Clear the current collection values and reintroduce in new order.
    $collection->clear();
    foreach ($values as $key => $item) {
        $collection->set($key, $item);
    }

    return $this;
}

然后可以像下面那样调用这个方法来解决原始问题

$list->orderCollection('listItems', array('getCategory' => 'ASC', 'getASecondPropertyToSortBy' => 'DESC'))