2

I have a "Post" object, accessed via the IOC container. Various errors tell me this object's type ends up as a "Collection", which implements several interfaces, including IteratorAggregate and ArrayAccess.

I want to display a user-defined group of posts according to a specific order, e.g.:

$desired=array("69","63","70");//these represent post id's

Just sorting an array in this manner seems complex, but I want to sort my collection. I have been researching various combinations of usort(), uksort(), Eloquent's sortBy(), array_multisort()... but the most obvious solutions result in orders like 3,2,1 or 1,2,3, not 2,1,3.

The closest I have gotten to this goal is to fetch the ones I want,

//BlogController
private function myposts($desired){
    $posts=$this->post->whereIn('id',$desired)->get();
    ...

"convert" the Collection object to an array,

$posts=$posts->toArray();

and treat the array with a custom function: source

function sortArrayByArray($array,$orderArray) {
    $ordered = array();
    foreach($orderArray as $key) {
        if(array_key_exists($key,$array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }
    return $ordered + $array;
}
$sorted=sortArrayByArray($array1,$desired);

I can then vardump the array in the correct order, but since it is now an array, I can't access the $posts object in my view. Can I convert the array back into a post object?

This whole approach feels wasteful anyway, converting to an array and back... is it? Is there a more straightforward way of sorting the contents of a "Collection"?

This is a little better, perhaps (a native php function):

array_multisort($desired,$posts,SORT_STRING);
//only works with string keys
//$desire=(1,2,3) will not work!

Again, this works for arrays, but attempting directly on the "posts" object fails...

Finally, I discovered using a Presenter: https://github.com/robclancy/presenter#array-usage which works in the view, after one of the above is completed in the controller:

@foreach($posts as $post)
<?php $post=new PostPresenter($post) ?>
{{View::make('site.post.article')->with(compact('post'))}}
@endforeach

This finally works, but it still feels like a long way to do it. Is there a better way to accomplish this task? Performance concerns or best practices with one method vs. another? Thanks in advance for anyone able to help.

Community
  • 1
  • 1
Ryan
  • 5,959
  • 2
  • 25
  • 24

2 Answers2

2

You can use the Collections own sort method and pass in a callback. The callback compares every two values in your collection tells the sort method which one is "higher". The sort method then sorts them accordingly. If you want a specific value order you just create mapping and sort by that. For more info check uasort

$posts->sort(function($a, $b){
    $desired=array("69" => 0,"63" => 1,"70" =>2);
    if(!array_key_exists($a,$desired) || !array_key_exists($b,$desired) || $a == $b){
        return 0
    }
    else{
        return ($desired[$a] < $desired[$b]) ? -1 : 1;
    }
});
tim
  • 3,191
  • 2
  • 15
  • 17
1

Alternatively to @tim's answer, you can re-assign the sorted array to a new Collection object:

$postsArray = $this->posts->toArray();

// Do some sorting/processing, then:

$newCollection = new \Illuminate\Database\Eloquent\Collection( $postsArray );
fideloper
  • 12,213
  • 1
  • 41
  • 38
  • When I try this and then try to loop through $postsArray and reference $post->name or any attribute of a collection item I get error: 'Trying to get property of non-object' – phirschybar Jan 18 '14 at 03:16