Ordering Doctrine Collection Based on Associated Entity When It Is Not Possible to Use the @Orderby Annotation

Doctrine orderBy annotation. Ordering Entity associations based on an associated Entity.

To solve this issue, I added the following method to the abstractEntity that all of our entities extend, and therefore all entities can sort their collections.

The following code has hasn't under gone any tests, but it should be a good starting point for anyone that might have this issue in future.

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

This method then could then be called somewhat like the below to solve the original question

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

Doctrine2: order associated entity by property of another entity related to it

Take a look to these 5 solutions described at question below:

Ordering Doctrine Collection based on associated Entity when it is not possible to use the @orderBy annotation

usort a Doctrine\Common\Collections\ArrayCollection?

To sort an existing Collection you are looking for the ArrayCollection::getIterator() method which returns an ArrayIterator. example:

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

The easiest way would be letting the query in the repository handle your sorting.

Imagine you have a SuperEntity with a ManyToMany relationship with Category entities.

Then for instance creating a repository method like this:

// Vendor/YourBundle/Entity/SuperEntityRepository.php

public function findByCategoryAndOrderByName($category)
{
return $this->createQueryBuilder('e')
->where('e.category = :category')
->setParameter('category', $category)
->orderBy('e.name', 'ASC')
->getQuery()
->getResult()
;
}

... makes sorting pretty easy.

Default sort for doctrine model with annotations

EDIT: This is not possible with defaults. Entities fetched from repositories are fetched by the provided sorting criteria:

$entities = $entityRepository->findBy(array(), array('field' => 'ASC'));

This, DQL and the Criteria API are the current ways of fetching entities with a given sorting criteria.

What the question at " Default sort attribute for Doctrine Model " is about is the sorting of collection-valued associations, which has nothing to do with direct fetching of entities from repositories.

For those associations, the annotation-equivalent of " Default sort attribute for Doctrine Model " is following (original answer):

As of the official annotations documentation for Doctrine 2 ORM, the annotation for default sorting conditions of collection-valued associations @OrderBy({"field" = "ASC", "otherField" = "DESC"}).

Here's how you would use it:

/**
* @ORM\OneToMany(targetEntity="Users")
* @ORM\OrderBy({"username" = "ASC"})
*/
protected $users;

Doctrine 2.1: How to orderBy aggregate field in a collection?

You need math operation in ORDER BY statement - in SQL it would be ORDER BY visited/shown DESC. I think it's not possible to do within annotations.



Related Topics



Leave a reply



Submit