Заказ коллекции Doctrine на основе связанного объекта, когда невозможно использовать аннотацию @orderBy

Я хотел бы понять, как лучше всего заказать коллекцию Doctrine на основе связанного Entity. В этом случае нельзя использовать аннотацию @orderBy.

Я нашел 5 решений в Интернете.

1) Добавление метода к AbstractEntity (согласно Ian Belter https://stackoverflow.com/a/22183527/1148260 )

/** * 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 // ie -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; } 

2) Создание расширения Twig, если вам нужна сортировка только в шаблоне (согласно Kris https://stackoverflow.com/a/12505347/1148260 )

 use Doctrine\Common\Collections\Collection; public function sort(Collection $objects, $name, $property = null) { $values = $objects->getValues(); usort($values, function ($a, $b) use ($name, $property) { $name = 'get' . $name; if ($property) { $property = 'get' . $property; return strcasecmp($a->$name()->$property(), $b->$name()->$property()); } else { return strcasecmp($a->$name(), $b->$name()); } }); return $values; } 

3) Преобразование коллекции в массив, а затем ее сортировку (согласно Benjamin Eberlei https://groups.google.com/d/msg/doctrine-user/zCKG98dPiDY/oOSZBMabebwJ )

 public function getSortedByFoo() { $arr = $this->arrayCollection->toArray(); usort($arr, function($a, $b) { if ($a->getFoo() > $b->getFoo()) { return -1; } //... }); return $arr; } 

4) Использование ArrayIterator для сортировки коллекции (согласно nifr https://stackoverflow.com/a/16707694/1148260 )

 $iterator = $collection->getIterator(); $iterator->uasort(function ($a, $b) { return ($a->getPropery() < $b->getProperty()) ? -1 : 1; }); $collection = new ArrayCollection(iterator_to_array($iterator)); 

5) Создание сервиса для сбора упорядоченной коллекции, а затем заменить неупорядоченный (у меня нет примера, но я думаю, что это довольно ясно). Я думаю, что это самое уродливое решение.

Какое наилучшее решение по вашему опыту? У вас есть другие предложения по заказу коллекции более эффективным / элегантным способом?

Большое спасибо.

Solutions Collecting From Web of "Заказ коллекции Doctrine на основе связанного объекта, когда невозможно использовать аннотацию @orderBy"

посылка

Вы предложили 5 действительных / достойных решений, но я думаю, что все можно было сократить до двух случаев, с некоторыми незначительными вариантами.

Мы знаем, что сортировка всегда O(NlogN) , поэтому все решения теоретически имеют одинаковую производительность. Но так как это Доктрина, узкие места являются числом SQL-запросов и методов гидратации (т.е. преобразования данных из массива в экземпляр объекта).

Поэтому вам нужно выбрать «лучший метод», в зависимости от того, когда вам нужны загружаемые объекты и что вы будете с ними делать.

Это мои «лучшие решения», и в общем случае я предпочитаю свое решение A)

A) DQL в службе загрузчика / хранилища

Похожий на

Ничего из вашего дела (как-то с 5, см. Окончательную заметку). Альберто Фернандес указал вам в правильном направлении в комментарии.

Лучшее, когда

DQL является (потенциально) самым быстрым методом, поскольку сортировка делегатов в СУБД, которая оптимизирована для этого. DQL также дает полный контроль над тем, какие объекты будут извлекаться в одном запросе и режиме гидратации.

Недостатки

Невозможно (AFAIK) изменить запрос, созданный классами Proctrine Proxy, по конфигурации, поэтому вашему приложению необходимо использовать репозиторий и вызывать соответствующий метод каждый раз, когда вы загружаете свои сущности (или переопределяете значение по умолчанию).

пример

 class MainEntityRepository extends EntityRepository { public function findSorted(array $conditions) { $qb = $this->createQueryBuilder('e') ->innerJoin('e.association', 'a') ->orderBy('a.value') ; // if you always/frequently read 'a' entities uncomment this to load EAGER-ly // $qb->select('e', 'a'); // If you just need data for display (eg in Twig only) // return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY); return $qb->getQuery()->getResult(); } } 

B) Яркая загрузка и сортировка в PHP

Аналогично случаю

Случай 2), 3) и 4) – это то же самое, что и в другом месте. Моя версия – это общий случай, который применяется всякий раз, когда объекты извлекаются. Если вам нужно выбрать один из них, то я думаю, что решение 3) является наиболее удобным, так как не возиться с сущностью и всегда доступно, но используйте загрузку EAGER (читайте дальше).

Лучшее, когда

Если ассоциированные объекты всегда читаются, но невозможно (или удобно) добавлять службу, тогда все объекты должны загружаться EAGER-ly. Сортировка может быть выполнена PHP, когда это имеет смысл для приложения: в прослушивателе событий, в контроллере, в шаблоне ветви … Если объекты должны быть всегда загружены, то лучше всего использовать прослушиватель событий.

Недостатки

Менее гибкий, чем DQL, и сортировка в PHP может быть медленной операцией, когда коллекция большая. Кроме того, объекты должны быть гидратированы как объекты, которые медленны, и переполняют, если коллекция не используется для других целей. Остерегайтесь ленивой загрузки, так как это вызовет один запрос для каждого объекта.

пример

MainEntity.orm.xml:

 <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping> <entity name="MainEntity"> <id name="id" type="integer" /> <one-to-many field="collection" target-entity="LinkedEntity" fetch="EAGER" /> <entity-listeners> <entity-listener class="MainEntityListener"/> </entity-listeners> </entity> </doctrine-mapping> 

MainEntity.php:

 class MainEntityListener { private $id; private $collection; public function __construct() { $this->collection = new ArrayCollection(); } // this works only with Doctrine 2.5+, in previous version association where not loaded on event public function postLoad(array $conditions) { /* * From your example 1) * Remember that $this->collection is an ArryCollection when constructor is called, * but a PersistentCollection when are loaded from DB. Don't recreate the instance! */ // Get the values for the ArrayCollection and sort it using the function $values = $this->collection->getValues(); // sort as you like asort($values); // Clear the current collection values and reintroduce in new order. $collection->clear(); foreach ($values as $key => $item) { $collection->set($key, $item); } } } 

Итоговые заметки

  • Я не буду использовать случай 1) как есть, так как это очень сложно и ввести наследование, которое уменьшает инкапсуляцию. Кроме того, я думаю, что он имеет такую ​​же сложность и производительность моего примера.
  • Случай 5) не обязательно является плохим. Если «служба» – это репозиторий приложений, и она использует DQL для сортировки, то это мой первый лучший случай. Если это настраиваемая услуга только для сортировки коллекции, то я думаю, что это не очень хорошее решение.
  • Все коды, которые я написал здесь, не готовы для «copy-paste», поскольку моя цель состояла в том, чтобы показать мою точку зрения. Надеюсь, что это будет хорошей отправной точкой.

отказ

Это «мои» лучшие решения, как я это делаю в своих работах. Надежда поможет вам и другим.