Skip to content
Advertisement

Laravel collection multi level groupBy insert new items without checking each level’s key

Laravel collections now have a great multi-level groupBy function built in, but I am struggling with a sensible way to insert new items.

For example:

$this->myCollection = EloquentModel::all()->groupBy(['key1','key2','key3','key4']);

Very nice, easy to set up and access. For this example, I will assume each key is a number

$this->myCollection[1][2][3][4] = new EloquentModel([insert => stuffHere]);

If there are already items in the nested positions [1], [2], and [3] then I can add a new item or overwrite the existing item at [4], but if any of the nested positions 1 through 3 is missing I get an error

Undefined offset: 1   (or 2 or 3 depending upon which is missing)

At the moment I am calling an insert into structure function with:

if (!$this->myCollection->has($1)) {
    $this->myCollection[$1] = new Collection;
}
if (!$this->myCollection[$1]->has($2)) {
    $this->myCollection[$1][$2] = new Collection;
}
if (!$this->myCollection[$1][$2]->has($3)) {
    $this->myCollection[$1][$2][$3] = new Collection;
}
$this->myCollection[$1][$2][$3][$4] = $itemFor4;

I find the groupBy nesting very useful, apart from not handling my inserts cleanly.

Some sample data – please assume there are > 100k records like this:

['username'=> 'a user name', 'course', => 'a coursename', 'activity_type' => 'one of 100s of activity names', 'reporting_week' => 23 // One of 52 weeks, [lots more data]]
['username'=> 'another user name', 'course', => 'another coursename', 'activity_type' => 'one of 100s of activity names', 'reporting_week' => 23 // One of 52 weeks, [lots more data]]
['username'=> 'a user name', 'course', => 'another coursename', 'activity_type' => 'one of 100s of activity names', 'reporting_week' => 24 // One of 52 weeks, [lots more data]]
['username'=> 'another user name', 'course', => 'a coursename', 'activity_type' => 'one of 100s of activity names', 'reporting_week' => 24 // One of 52 weeks, [lots more data]]

Instead of username and course in real life it would be a code representing that specific user and that course.

The data would include minutes of activity, counts of activity, grade of activity, how early or late the activity was etc.

Advertisement

Answer

I think you could extend the collection, adding a custom insertDeep function.

An idea:

Collection::macro('insertDeep', function (array $path, $value) {
    $key = array_shift($path);
   
    if (count($path) === 0) {
        $this->put($key, $value);
        return $this;
    }

    if (!$this->has($key)) {
        $this->put($key, new Collection());
    }

    return $this->get($key)->insertDeep($path, $value);
});

Then you could simply call this method on your collection:

$this->myCollection->insertDeep([$1, $2, $3, $4], $itemFor4);

I just tried it and it should work properly:

$coll = new Collection();
$coll->insertDeep(['a', 'b', 'c', 'd'], 'test');

$coll->toArray();
// Result
   [
     "a" => [
       "b" => [
         "c" => [
           "d" => "test",
         ],
       ],
     ],
   ]

Disclaimer: this function will fail if you specify a path where a key already exists but whose value is not a collection

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement