13

I'm looking for a clean way to reverse the order of a Doctrine_Collection. I know it sounds weird, so let me explain my (simple) goal: I need to display the x latest/newest record, but I have to display it in the reverse order: the oldest 1st, etc.

If it's unclear, here is an example : Lets say I have this in my table (lets call it 'example') :

id      date
1       2012-01-21
2       2012-03-19
3       2012-02-21
4       2012-03-21

So far, I've done this:

Doctrine::getTable('Example')->createQuery('d')
    ->orderBy('date DESC')
    ->limit(3);

Which returns that

id      date
4       2012-03-21
2       2012-03-19
3       2012-02-21

But I want that:

id      date
3       2012-02-21
2       2012-03-19
4       2012-03-21

Edit:

I've found a solution to this, using intermediate array & using array_reverse on it. But it doesn't look good :(

Here is the code I've written:

    $query = Doctrine::getTable('Example')
            ->createQuery('e')
            ->orderBy('date DESC')
            ->limit(3)
        $collection = $query->execute();

        //Here is the dirty hack:
        $itemArray = array();
        foreach ($collection as $item) {
            $itemArray[] = $item;
        }
        $itemArray = array_reverse($itemArray);
        $orderedCollection = new Doctrine_Collection($doctrineClass);
        foreach($itemArray as $item) {
            $orderedCollection->add($item);
        }
        //OrderedCollection is OK but... come on! There must be a cleaner way to do it

Edit 2 : answer from @Adam Kiss

        $query = Doctrine::getTable('Example')
            ->createQuery('e')
            ->orderBy('date DESC')
            ->limit(3)
        $collection = $query->execute();

        //Here is the **lovely** hack:
        $orderedCollection = new Doctrine_Collection('Example');
        for ($i=($collection->count() - 1); $i>=0;$i--) {
            $orderedCollection->add($collection->get($i));
        }
haltabush
  • 4,368
  • 2
  • 21
  • 40

9 Answers9

18

Without intermediate array:

for ($i = $collection->count(); $i > 0; $i--) {
    $orderedCollection->add($collection->get($i));
}

Hopefully good answer:

You could export Collection to array and reverse it

$query = Doctrine::getTable('Example')
         ->createQuery('e')
         ->orderBy('date DESC')
         ->limit(3)
$collection = $query->execute();
$collection = array_reverse($collection->toArray());

Old (wrong) answer:

Maybe you should use

Doctrine::getTable('Example')->createQuery('d')
  ->orderBy('date ASC')
  ->limit(3);
Yes Barry
  • 8,896
  • 4
  • 43
  • 63
Adam Kiss
  • 11,411
  • 8
  • 46
  • 79
  • No, if I do so I won't have the 3 newest records, but the 3 oldest. – haltabush Mar 21 '12 at 10:52
  • 3
    I thought about that too, but the problem is that toArray also convert the Doctrine_Records to arrays... And I definitly don't want that :) – haltabush Mar 21 '12 at 11:04
  • Congrats, your final solution is the cleaner (to my mind). You just have a small typo on the for loop; $i should go until >0, not >=0 – haltabush Mar 26 '12 at 08:13
  • @haltabush oh yeah, I'll fix that :) – Adam Kiss Mar 26 '12 at 08:35
  • Oh, and the last minor detail: `->add($collection->get($i);` should be `->add($collection->get($i));`. But asside from that; thanx, it's pretty usefull to me – Pieter Verloop Feb 03 '15 at 11:33
  • 1
    Another issue with the `for loop`, is that `count` returns the total number of items and not the last item index. So looping over count will always be `+1` from the last index. eg: `count(['a']) = 1` where the index should be `0`. So to iterate over all of the available items in the collection in reverse order, it would need to be `$i=$collection->count()-1; $i >= 0; $i--` [See example](https://3v4l.org/tQPPB) However I suggest utilizing https://stackoverflow.com/a/9842781/1144627 to ensure intended iteration over the keys. – Will B. Jan 08 '18 at 18:12
5

It seems like you either have to customize Doctrine_collection to provide something similar to array_reverse within your collection-class or use some hackish approach like the following:

$keys = array_reverse($collection->getKeys());
foreach ($keys as $key) {
    $object = $collection->get($key);
}
dbrumann
  • 15,087
  • 1
  • 35
  • 52
5

If you don't want to use any special collection functionality, just iterate over of the elements in reverse order (without any overhead) you can do it pretty easy like this:

/** @var $results Doctrine_Collection this is your result collection */
$iterator = $results->getIterator();
$item = end($iterator);

do {
  var_dump($item);
} while ($item = prev($iterator));
1ed
  • 3,508
  • 12
  • 23
  • That's beautiful :) But, as you can guess, my query is in a model, and I actually want to return a instance of Doctrine_Collection. I'll probably use a mix of your solution and Mahok's (if no one else have a better idea) – haltabush Mar 23 '12 at 21:14
  • You can return a collection (as the `$results` variable is a collection) and use this in the view (probably in a partial for reusability)... – 1ed Mar 23 '12 at 21:24
  • 1
    True, it's just a bit strange to do that in the view :) – haltabush Mar 23 '12 at 21:26
  • haltabush is correct, should not being iterating the collection in the view, unless you localize it in the controller then pass it to the view. – Mike Purcell Mar 23 '12 at 21:44
  • OK, then just do this in your model `return $results->setData(array_reverse($results->getData(), true));` – 1ed Mar 23 '12 at 23:54
  • @haltabush so what do you think about my last proposal? You can put it in the model and it returns a collection like you would, doesn't it? – 1ed Mar 24 '12 at 13:41
  • I'll try it on Monday. Thanks! – haltabush Mar 24 '12 at 15:28
  • I'll use Adam's solution, I think it's easier to read (at least for me ^^) – haltabush Mar 26 '12 at 08:13
  • I like this solution more but `prev()` is not implemented in `ArrayIterator` in 2014/2015 (using Doctrine 2.0 ODM). This is because the data is coming as a stream, and is not getting fetched all at once (using the ODM::QueryBuilder with Mongo, not ORM/MySQL). – Daniel W. Jan 05 '15 at 11:20
0

If you want an ArrayCollection object, another solution to this which expands on the answer given by @adam-kiss is to use the returned value of array_reverse to construct a new ArrayCollection object which reduces the need of a for loop.

use Doctrine\Common\Collections\ArrayCollection;

$query = Doctrine::getTable('Example')
    ->createQuery('e')
    ->orderBy('date DESC')
    ->limit(3)
;
$collection = $query->execute();
$orderedCollection = new ArrayCollection(array_reverse($collection->toArray()));
ghindle
  • 530
  • 2
  • 10
0

By the looks of it, surely just remove "DESC" from the orderby. The query returns in date descending order exactly as it's told and you want it in ascending order.

Chris
  • 375
  • 2
  • 10
0

For Doctrine\ORM\PersistentCollection

/** @var Doctrine\ORM\PersistentCollection $posts */
$posts = $category->getPosts();
for ($i = $posts->count()-1; $i >= 0; $i--) {
    $post = $posts->offsetGet($i);
    // ... do some logic
}
0

it worked for me. doctrine version: 1.2

    $collection = //make doctrine collection;
    $reversed_array = array_reverse($collection->toArray());
    $collection->fromArray($reversed_array);
0
use Doctrine\Common\Collections\Criteria;
...

$criteria = Criteria::create();
$criteria->orderBy(['id' => 'DESC']);

return $this->objections->matching($criteria);
Ramazan APAYDIN
  • 746
  • 3
  • 10
  • 23
0

maybe you should use like this

     ->orderBy('date', 'DESC')

because in this documentation http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/query-builder.html orderby's signature requires ASC/DESC as second parameter

      public function orderBy($sort = null, $order = null);
Ekrem
  • 473
  • 2
  • 16
  • Well, yes, but it's not a big deal anyway. I've always used is this way, it works too; it's also less confusing when you have multiple orderBy clauses – haltabush Mar 27 '12 at 22:01
  • 1
    i see... if you dont mind using raw sql with doctrine this might help SELECT * FROM ( SELECT * FROM `Example` ORDER BY DATE DESC LIMIT 3 )foo ORDER BY 2 ASC – Ekrem Mar 27 '12 at 22:54
  • I do mind about raw sql, sorry :) I thought about that too. By the way, the accepted answer is OK for me. I just forgot to end the bounty (I thought it was automatic) – haltabush Mar 27 '12 at 22:57