I would like to understand the best way to order a Doctrine Collection based on associated Entity. In this case, it is not possible to use the @orderBy annotation.
I have found 5 solutions on the Internet.
1) Adding a method to the AbstractEntity (according to 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
// 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;
}
2) Creating a Twig extension, if you need the sorting just in a template (according to 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) Transforming the collection into an array and then sorting it (according to 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) Using ArrayIterator to sort the collection (according to 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) Creating a service to gather the ordered collection and then replace the unordered one (I have not an example but I think it is pretty clear). I think this is the ugliest solution.
Which is the best solution according to you experience? Do you have other suggestions to order a collection in a more effective/elegant way?
Thank you very much.