8

I'm trying to give a copy of a collection of users to an eloquent model jobs. So I'd effectively have:

jobs : [
    1 : {
        users : {
            1: {}
            2: {}
            3: {}
        }
    }
    2 : {
        users : {
            1: {}
            2: {}
            3: {}
        }
    }
]

Once I get this, I'm going to sum some numbers from another query to essentially give myself a total for each user on each job, so the above may end up looking like this:

jobs : [
    1 : {
        users : {
            1: {
                total: 120
            }
            2: {
                total: 45
            }
            3: {
                total: 12
            }
        }
    }
    2 : {
        users : {
            1: {
                total: 32
            }
            2: {
                total: 4
            }
            3: {
                total: 17
            }
        }
    }
]

Except I can't seem to clone this users list, and I'm effectively ending up with all the totals being the same as each other:

{  
   1:{  
      id:1,
      users:{  
         1:{  
            id:1,
            total:807
         },
         2:{  
            id:2,
            total:9
         }
      }
   },
   2:{  
      id:2,
      users:{  
         1:{  
            id:1,
            total:807
         },
         2:{  
            id:2,
            total:9
         }
      }
   },
   3:{  
      id:3,
      users:{  
         1:{  
            id:1,
            total:807
         },
         2:{  
            id:2,
            total:9
         }
      }
   }
}

Here is what I am doing:

public function summary()
{
    $jobs = Job::all()->keyBy('id');
    $users = User::with('closed_time_chunks')->get();

    $users_list = User::all(['id'])->keyBy('id');

    // I think this is the problem statement:
    foreach ($jobs as $job):
        $job->users = clone $users_list;
    endforeach;

    Log::info('Starting');

    
    foreach ($users as $user):
        foreach ($user->closed_time_chunks as $chunk):

            Log::info('Adding ' . ($chunk->stop_time - $chunk->start_time) . ' to job: ' . $chunk->job_id);
            $jobs[$chunk->job_id]->users[$chunk->user_id]['total'] += $chunk->stop_time - $chunk->start_time;

        endforeach;
    endforeach;
}

My guess is that I am actually just creating a reference to the same thing and any addition is in fact just adding to the 'master' collection. How can I successfully clone the users so that the totals will be unique across jobs?

Edit

Using an array (as Matheos recommends) results in a really bizarre error:

ErrorException (E_NOTICE)

Indirect modification of overloaded property Job::$users has no effect

Community
  • 1
  • 1
Djave
  • 8,595
  • 8
  • 70
  • 124

3 Answers3

25

Your problem is that you are cloning your $users_list, but that is a Collection of User objects. In PHP, when you clone an object, any of it's properties that are references to objects remain references to those objects, in other words those child objects do not get cloned themselves. See __clone

Being that your code is dynamically adding a 'total' property to every User instance in the Collection, it is effectively altering that total value of all instances of that particular User, because they are all references to themselves. What you would need to do is clone every child member (User) of your Collection, along with the Collection itself.

foreach ($jobs as $job):
    $job->users = clone $users_list;
    $job->users->transform(function($user) { return clone $user; });
endforeach;

There are probably better ways to do what you're trying to do, but this should get you going and hopefully answer your question of why too.

Jeff Berry
  • 266
  • 2
  • 2
  • Ahhh... I think somewhere in between grabbing all the data and trying to assign it to different people, I kind of forgot about the whole Model deal and was treating the Collection as _just_ an Array. Which of course it wasn't / isn't. Your answer really, really helped. – Djave Apr 22 '15 at 10:55
  • Thanks for the solution this saved my time. – Poorna Rao Jun 17 '19 at 13:48
0

You can achieve deep copy by serializing/unserializing Eloquent collection (or any other capable object). But bear in mind that this can be resource costly operation depending on the complexity of the structure. For simple objects it is quite effective.

$newJobs = unserialize(serialize($jobs))
6wizard
  • 1
  • 1
-1

Instead of an eloquent collection try using an array:

$users_list = User::all(['id'])->keyBy('id')->toArray();

You may also want to set an initial value for 'total'

foreach ($users as $user) {
    foreach ($user->closed_time_chunks as $chunk) {

        Log::info('Adding ' . ($chunk->stop_time - $chunk->start_time) . ' to job: ' . $chunk->job_id);

        if (! isset($jobs[$chunk->job_id]->users[$chunk->user_id]['total'])) {
            $jobs[$chunk->job_id]->users[$chunk->user_id]['total'] = 0;
        }

        $jobs[$chunk->job_id]->users[$chunk->user_id]['total'] += $chunk->stop_time - $chunk->start_time;

    }
}
Matheos
  • 116
  • 6
  • I actually tried that initially, but then ended up going back to the above as it gave the following error: Indirect modification of overloaded property Job::$users has no effect Do you know what this refers to? – Djave Apr 20 '15 at 08:51