neilcarpenter's avatar

Custom order of collection

I have used the merge() collection method to combine 3 different models collections that are Pages, Posts & Photos.

So now I have collection that looks like this...

['post', 'post', 'post', 'post', 'post', 'post', 'photo', 'photo', 'page', 'page']

Ideally I'd like to reorder this collection so that it's like...

['post', 'post', 'post', 'page', 'photo', 'photo', 'page', 'post', 'post', 'post']

Anybody have any idea what might be the best way to tackle this?

Thanks

0 likes
10 replies
selenearzola's avatar

Hello Neil, right now I'm in the same situation, have you achieve this? Any comment or suggestion would be greatly appreciated!

biishmar's avatar
biishmar
Best Answer
Level 11

Dont use merge, split the post and page collection by count/2

$post = Post::all() // can use normal query builder to get the post you want
$post = $post->split($post->count() / 2)->toArray()

$page= Page::all(); // can use normal query builder to get the page you want
$page= $page->split($page->count() / 2)->toArray()

$photo = Photo::all(); // can use normal query builder to get the photo you want
$photo = $photo->toArray();

$collection = collect($post[0], $page[0], $photo, $page[1], $post[1])->collapse()->toArray();

dd($collection);


1 like
austenc's avatar

Firstly, I'd recommend against using merge in this case. What happens if you have a post and a photo that both have an ID of 1? One will get left out (try it!).

I'd recommend using a combination of concat() and sortBy with a callback method:

$posts->concat($pages)->concat($photos)->sortBy(function ($model) {
    return class_basename($model);
});

neilcarpenter's avatar

@austenc - Using merge doesn't leave any items out.

I have a public $items property on my class and then a method that does this (simplified for the sake of this thread)

$posts  = collect(Post::limit(10)->get());
$photos = collect(Photo::limit(4)->get());
$work   = collect(Work::limit(4)->get());

$this->items = $this->items->merge($posts);
$this->items = $this->items->merge($photos);
$this->items = $this->items->merge($work);
        
dd($this->items->lists('id'));

I've given the following array

Collection {#974 ▼
  #items: array:20 [▼
    0 => 1
    1 => 2
    2 => 3
    3 => 4
    4 => 5
    5 => 6
    6 => 7
    7 => 16
    8 => 17
    9 => 18
    10 => 1
    11 => 2
    12 => 3
    13 => 4
    14 => 1
    15 => 2
    16 => 3
    17 => 4
  ]
}
neilcarpenter's avatar

@snapey - my original idea wasn't just a random order, it had to specifically be that order. For the sake of rendering it out on the frontend.

@selenearzola - I ended up changing my way approach to this as it was part of a cms for a cilent, and there could potentially be no photos or posts set to be featured on the homepage. Which is where I wanted the collection to be displayed.

Instead i made sure all items had a published date attached to them, and ordered the $items collection by published date once i'd merged them all together.

I had to do a bit of trickery in my view, using chunks & rendering out partials based on what type of model each $item was to make sure everything looked nice regardless of what order the $items were in.

Snapey's avatar

@austenc - Using merge doesn't leave any items out.

provided you cast each Eloquent collection to a support collection first

2 likes
austenc's avatar

@Snapey is that how it works? Have run into trouble with merge doing that in some cases but couldn't remember, thanks for the clarification!

selenearzola's avatar

@neilcarpenter What I was trying to achieve last week was to order a collection of objects that have a different type (post, video, article, user feed, etc) before to render it in a view. The way I've achieved was:

1.- I got the user's feed using a custom query and ordered per its creation_date and converted this collection to an array ($posts).

2.- Got the publications info, classified per type and added to my posts array. Like this:

foreach (self::$LIST_TYPES as $name => $type) { $publications = self::getPublicationsFromFilters(['type' => $name])->toArray(); $posts[$name] = $publications; }

According to the code above my posts array look like this:

$posts = [ 'userFeed' => [0=> [], 1 => []], 'video' => [0=> [], 1 => []], 'post' => [0=> [], 1 => []], ...... ]

3.- I've created an array in order to establish the order of the posts [post, video, post, article, user_feed].

4.- Using a while loop I've iterated my ordered array like this:

while ($chunk) {
       
        $itemFound = false;

        foreach ($postOrder as $type) {

            if (array_key_exists($type, $posts) && count($posts[$type])) {
                $itemFound = true;
                array_push($communityFeed, array_shift($posts[$type]));
                $chunk--;
            }
        }

        if (!$itemFound) {
            $chunk--;
        }
    }

Chunk var stands for the limit of items to be used from the post array. I would like to set its value from the array's total of items.

Hope it helps to someone in the future, also if there is any idea/suggestion that helps me to improve this it would be greatly appreciated!

neilcarpenter's avatar

Hi @selenearzola

Is your records collection all from the same model, and does each one of the records have a type property?

Or do you have seperate models for Post, Video, Article & Feed and combined them all together?

I'm just trying to get a better understanding of what you're trying to do because I think you can improve your code.

1 like

Please or to participate in this conversation.