6

The Magento collection sorting functions (e.g. Mage_Eav_Model_Entity_Collection_Abstract::addAttributeToSort) work by adding an ORDER BY clause to the SQL select statement. However, there are times when a collection has already been loaded and it is necessary to sort the collection.

It is certainly possible to use the toArray($fields) function and then PHP array sorting functions (either native or user-defined), however this is a little clumsy. It also means that the objects in the collection are converted to "dumb" rows of values without magic getters/setters which can/are be implemented with algorithms, etc.

I'm wondering if there are more elegant/Magento-esque methods of sorting the collection.

Thanks,
Jonathan

Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
  • So basically what you are wondering is if you already have a collection sorted by one of the attributes you again want to sort by another attribute..? – Subesh Pokhrel Apr 15 '11 at 08:33
  • @SubeshPokhrel yes, but that's not the key issue. The key issue is sorting the collection by an attribute AFTER the collection's internal $_items have been populated (ie after SQL execution) – Jonathan Day Apr 15 '11 at 10:07
  • Why don't you add another sort query to the collection in that way you can have sort by two attributes and you get ordered collection with two attributes..? Or am I missing the whole point. – Subesh Pokhrel Apr 15 '11 at 10:32

5 Answers5

14

There is no proper way of doing it. But I think it is possible with using of Reflection. You can retrieve $_items property of collection object, sort them and set it back to the collection.

function sortCollection(Varien_Data_Collection $collection, callable $sorter) {
    $collectionReflection = new ReflectionObject($collection);
    $itemsPropertyReflection = $collectionReflection->getProperty('_items');
    $itemsPropertyReflection->setAccessible(true); // Make it accessible

    $collectionItems = $itemsPropertyReflection->getValue($collection);

    usort($collectionItems, $sorter);

    $itemsPropertyReflection->setValue($collection, $collectionItems);

    $itemsPropertyReflection->setAccessible(false); // Return restriction back

    return $collection;
}
Lee Saferite
  • 3,124
  • 22
  • 30
Ivan Chepurnyi
  • 9,233
  • 1
  • 43
  • 43
  • 3
    I confirm it's possible Ivan, my partner just use your snippet succesfully. The only thing is that instead of `$collection->getProperty('_items');` he had to use `$collectionReflection->getProperty('_items');`, a small typo I guess. One another thing I think worth telling, is that, as we're talking about Magento, and thus Classes, `$yourSortingCallback` has to be an array of 2 elements, first one being the name of the class where the method is defined, and 2nd one is the name of the method, ie: `usort($collectionItems, array('The_Class', 'The_Method'));`. Anyway, we're both very thankfull – OSdave Jun 15 '11 at 18:33
  • @OSdave, yeah there was a type. $yourSortingCallback call back can be even anonymous function. – Ivan Chepurnyi Jun 16 '11 at 17:23
4

Here's a tip; A collection's clear method unsets it's loaded flag, it allows you to change the sort or filters and run the new query.

I accidentally discovered it when answering load only configurable products.

Community
  • 1
  • 1
clockworkgeek
  • 37,650
  • 9
  • 89
  • 127
  • and of course it means you can also addAttributeToSelect() as well, which was just what I was struggling with, thanks :-) – benz001 Jun 28 '11 at 03:37
3

The method of @Ivan Chepurnyi worked but returns a ReflectionObject object, in my case I needed a Varien_Data_Collection.
Here is what I did instead

$collectionItems = $collection->getItems();
usort($collectionItems, array($this, '_sortItems'));
$newCollection = new Varien_Data_Collection();

foreach ($collectionItems as $item) {
    $newCollection->addItem($item);
}

var_dump($newCollection);

And in case here is the sorting method

public function _sortItems($a, $b)
{
    $columnId = "your_column_that_you_need_to_sort";
    $dir      = "desc";

    $al = strtolower($a->getData($columnId));
    $bl = strtolower($b->getData($columnId));

    if ($al == $bl) {
        return 0;
    }

    if ($dir == 'asc') {
        return ($al < $bl) ? -1 : 1;
    } else {
        return ($al > $bl) ? -1 : 1;
    }
}
Shadoweb
  • 5,812
  • 1
  • 42
  • 55
2

Another solution which works:

class Aligent_Navigation_Block_Dropdown extends Mage_Catalog_Block_Product_List {

public function getProductsByShortDesc(){
    $data = $this->getLoadedProductCollection()->getItems();  //an array of objects
    usort($data,array('Aligent_Navigation_Block_Dropdown','sortByShortDesc'));
    return $data;
}

public static function sortByShortDesc($a, $b)
{
  if($a->getShortDescription() ==  $b->getShortDescription()){ return 0 ; }
  return ($a->getShortDescription() < $b->getShortDescription()) ? -1 : 1;
}
}
Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
0

The above solution will work fine, but it's a LOT slower and more intensive than adding the sort to the query itself.

If you have a large collection you will use a huge amount of memory as you need to load an object (or objects) fore each result and store them all.

Using the collection Magento will only load one row at a time from the database which will be a lot more efficient than the above solution :-)

Andrew
  • 12,617
  • 1
  • 34
  • 48