Merging two Laravel collections
Asked Answered
S

3

5

My head hurts from working with Laravel collections. I have two collections:

    $dt = Carbon::now();
    $days = new Collection([]);

    /**
     * Create a calender month
     */
    for ($day = 1; $day <= $dt->daysInMonth; $day++) {
        $date = Carbon::create($dt->year, $dt->month, $day)->toDateString();
        $days->push(new Timesheet([
            'date' => $date,
        ]));
    }

    /**
     * Get all timesheets for user
     */
    $timesheets = Timesheet::where('user_id', $this->user->id)
        ->get();

\Illuminate\Database\Eloquent\Collection ($timesheets)

#attributes: array:5 [▼
    "id" => "1"
    "user_id" => "1"
    "date" => "2016-02-22 22:05:01"
    "created_at" => "2016-02-22 22:05:01"
    "updated_at" => "2016-02-22 22:05:01"
  ]
  // ... one or more ...

I have second collection giving me all days for a given month.

\Illuminate\Support\Collection ($days)

#attributes: array:1 [▼
    "date" => "2016-02-01 00:00:00"
]
// ... and the rest of the month.

I want to merge the $days collection with the $timesheet collection preserving the values of the $timesheet collection and removing any duplicates present in the $days collection. E. g. if $timesheets already contains '2016-02-24' I do not want to merge '2016-02-24' from $days. How do I do this?

Seals answered 23/2, 2016 at 20:29 Comment(15)
They aren't carbon objects just strings. Merge is likely the way to go as answer below. If you want to do some filtering use the filter method of the collectionGeostrophic
Your head would really hurt if there was no collections at all.Liatrice
@MikeMiller what I meant is that they are casted as Carbon instances. See: laravel.com/docs/5.1/eloquent-mutators#introductionSeals
So what do the collections look like?Geostrophic
@MikeMiller I've updated the original question.Seals
I am getting why merge isn't working. Can you show how the product should look?Geostrophic
@MikeMiller I've tried to describe it in the original question.Seals
Can you please update your question with the 2nd Query as well? How are you fetching the 2nd Collection.Macur
OK I get you. Merge should do this. What is the issue with the output of a merge? How is it wrong?Geostrophic
@Macur The second collection is a \Illuminate\Support\Collection which I generate on the fly containing all dates for a given month. See Collection 2.Seals
I see, in that case as @MikeMiller said, merge should work $timesheets->merge($days); Can you update the Result that you are getting with the merge?Macur
@Dwijen, I've updated the question with the result of the merge.Seals
Added an answer that might fix your issue, but I have no idea why the merge would merge only the last item.Macur
@Seals instead of calling new Collection([]), does the helper function collect([]) fix your issue?Apace
Nope. All collect() does is return a new Collection([]).Seals
G
1

OK have a go with this. Logic should pretty much work out but obv didnt have access to your Timesheet class..

$days = new Collection([]);

//basically the same structure i think
$timesheets = new Collection([new Collection(['date'=>'2016-02-23','created_at'=>'2016-02-23 14:12:34']),new Collection(['date'=>'2016-02-28','created_at'=>'2016-02-23 14:15:36'])]);

$dt = Carbon::now();

for ($day = 1; $day <= $dt->daysInMonth; $day++) {

    $date = Carbon::create($dt->year, $dt->month, $day)->format('Y-m-d');

    //filter your timesheets and see if there is one for this day
    $timesheet = $timesheets->filter(function($timesheet) use($date){return $timesheet->get('date')==$date;});

    if(!$timesheet->isEmpty()){
        //if there is a timesheet for today then add it to your $days collection
        $days->push($timesheet);
    }else{
        //otherwise just stick in the date
        $days->push(new Collection([
            'date' => $date,
        ]));
   }
}

//voila!
dd($days);
Geostrophic answered 24/2, 2016 at 17:59 Comment(4)
Thanks, this will work. Right now I am having problems getting get to work on my model: FatalThrowableError in Grammar.php line 107: Type error: Argument 1 passed to Illuminate\Database\Grammar::columnize() must be of the type array, string givenSeals
As your timesheet collection is a database collection the get is different. You will have to change the test in the filter. You could try casting as array and using laravel array_get('date') Geostrophic
Ah ... I see. This works for my collection when filtering: if (Carbon::parse($timesheet->date)->format('Y-m-d') == $date) {return $timesheet;}. Many thanks for the insight!Seals
No worries. You only have to return something that will evaluate to true from the filter so return Carbon::parse($timesheet->date)->format('Y-m-d') == $date should work and is a bit more conciseGeostrophic
A
5

Use merge:

$collection1 = Model1::all();
$collection2 = Model2::all();
$mergedCollection = $collection1->merge($collection2);

Documentation

The documentation talks about using it with arrays, but looking at the method signature it will take mixed arguments. Testing it on a local install of a Laravel 4 project worked for me.

Apace answered 23/2, 2016 at 20:33 Comment(2)
I have already tried this without getting the desired result. I'll give it another go.Seals
This doesn't work properly because it will replace items from one collection with another whose attribute contains the same key.Brillatsavarin
M
2

I am not sure why the $merged = $timesheets->merge($days); merged only the last item. Maybe someone else can shed some light on it.

But until there's a better solution, you can do this -

$merged = array_merge($timesheets->toArray(), $days->toArray());

Hope this helps.

Macur answered 24/2, 2016 at 9:40 Comment(3)
This just appends the entire content of $days to $timesheets.Seals
How do you want the final result then?Macur
I want to merge the $days collection with the $timesheet collection preserving the values of the $timesheet collection and removing any duplicates present in the $days collection. E. g. if $timesheet already contains '2016-02-24' I do not want to merge '2016-02-24' from $days. I apologize if I have been unclear on the desired result.Seals
G
1

OK have a go with this. Logic should pretty much work out but obv didnt have access to your Timesheet class..

$days = new Collection([]);

//basically the same structure i think
$timesheets = new Collection([new Collection(['date'=>'2016-02-23','created_at'=>'2016-02-23 14:12:34']),new Collection(['date'=>'2016-02-28','created_at'=>'2016-02-23 14:15:36'])]);

$dt = Carbon::now();

for ($day = 1; $day <= $dt->daysInMonth; $day++) {

    $date = Carbon::create($dt->year, $dt->month, $day)->format('Y-m-d');

    //filter your timesheets and see if there is one for this day
    $timesheet = $timesheets->filter(function($timesheet) use($date){return $timesheet->get('date')==$date;});

    if(!$timesheet->isEmpty()){
        //if there is a timesheet for today then add it to your $days collection
        $days->push($timesheet);
    }else{
        //otherwise just stick in the date
        $days->push(new Collection([
            'date' => $date,
        ]));
   }
}

//voila!
dd($days);
Geostrophic answered 24/2, 2016 at 17:59 Comment(4)
Thanks, this will work. Right now I am having problems getting get to work on my model: FatalThrowableError in Grammar.php line 107: Type error: Argument 1 passed to Illuminate\Database\Grammar::columnize() must be of the type array, string givenSeals
As your timesheet collection is a database collection the get is different. You will have to change the test in the filter. You could try casting as array and using laravel array_get('date') Geostrophic
Ah ... I see. This works for my collection when filtering: if (Carbon::parse($timesheet->date)->format('Y-m-d') == $date) {return $timesheet;}. Many thanks for the insight!Seals
No worries. You only have to return something that will evaluate to true from the filter so return Carbon::parse($timesheet->date)->format('Y-m-d') == $date should work and is a bit more conciseGeostrophic

© 2022 - 2024 — McMap. All rights reserved.