1

I have a question maybe a little silly. I'm trying to replicate and store a model instance in a variable to call the loadMissing method. The idea is to call the loadMissing method without being affected by the original model. Let me explain.

I have the following method in my User model:

class User extends Authenticatable
{
   ...

   public function myCustomMethod()
   {
        $cloned = $this->replicate()
            ->loadMissing(['roles', 'roles.permissions'])
            ->getAttribute('roles')
            ->flatMap(function ($role) {
                return $role->permissions
            });

        return $cloned;
   }

The problem is that in this way the original model instance is affected, and it loads in the original instance all the relations that I am loading only in this method. My question is: is there a way to load and isolate relationships in a replica of the model instance without affecting the original model?

Thanks in advance.

EDIT 1

I have also tried cloning the instance and trying to use the loadMissing method on the cloned instance, but the original instance is also affected. For example:

class User extends Authenticatable
{
   ...

   public function myCustomMethod()
   {
        $original = $this;
        $cloned = clone $original;

        $cloned->loadMissing('roles.permissions');

        dump($original);
        dump($cloned);

        die();
   }

But in this way, I get the following results. Notice that both the original and the cloned instance are loading the relationships when I'm just trying to load the relationships in the cloned instance:

$original

1

$cloned

imagen

I was wondering if this is normal behavior. If it is a normal behavior, which I suppose it is not, I was wondering if there is any way to use the loadMissing method on a copy, replica or clone of an instance without modifying the original instance. Thank you very much in advance.

EDIT 2

My controller looks like simple like this:

class HomeController extends Controller
{
    public function index()
    {
        dd(User::find(1)->myCustomMethod());
    }
}

SOLUTION

After several days looking for documentation, doing tests and pulling my hair a little, I have found the solution to my problem and share it in case someone comes here looking for similar information in the future.

Basically the answer that helped me find the right way was this:

https://stackoverflow.com/a/29787693/13066106

Which is documented in the official PHP documentation:

https://www.php.net/manual/en/language.oop5.cloning.php

I copy and paste verbatim the most relevant here:

When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.

When I got to this point, I understood why it wasn't working ... I was cloning a collection of type Illuminate\Database\Eloquent\Collection and this collection contained multiple objects of type Role, but according to the documentation, it is normal behavior that when I modify any property in the cloned instance, the changes will be reflected back to me in the original instance.

From here, I looked for more documentation about it and I found an article that definitely helped me solve my problem:

Don't clone your php objects, DeepCopy them

The article is quite descriptive and basically what it does is advise the use of the package DeepCopy

Fortunately, the package is already a Laravel dependency by default, so I didn't have to install it with composer. I just used the deep_copy method to clone the instance.

Thank you very much to everyone who helped me in some way. I hope that the links to the documentation that I have shared will be of use to those who come here looking for similar information.

leo95batista
  • 699
  • 9
  • 27
  • I guess you need the `clone()` method to clone the query instead of `replicate()` method. try this untested code : `$cloned = clone $this; $cloned->loadMissing(['roles])` – iamab.in Aug 17 '21 at 02:12
  • Thank you very much for your comment. Unfortunately I have not achieved the expected results with what you tell me. The truth was I had a lot of faith in your answer. I have updated the question, please if you could take a look at it. Thanks a lot. – leo95batista Aug 18 '21 at 02:56
  • Can you share the related controller code? – iamab.in Aug 18 '21 at 20:34
  • @iamab.in Sure!. See my EDIT 2. Thanks – leo95batista Aug 19 '21 at 19:08
  • sorry for the late reply. With the following modifications `clone()` works in my side. `$user = User::find(1); $userClone = clone $user; $user->load(['roles' => function($qry) { // some condition }]); $clonedUserRoles = $userClone->loadMissing(['roles']);` In this example `$user` not modified by the `loadMissing()` method called on `$userClone`. – iamab.in Aug 26 '21 at 09:16
  • please note, in this method, `clone` should be made before loading relations. The point to be noted is that the `loadMissing()` actually runs the query to get the whole relation. hence if you prefer to run 1 query less, you can call `$clonedUserRoles = $userClone->loadMissing(['roles']);` before `$user->load(['roles' => function($qry) { // some condition }]);` – iamab.in Aug 26 '21 at 09:22
  • if more clarifications are needed, I will add an answer. – iamab.in Aug 26 '21 at 09:32

1 Answers1

2

Why don't you just get the permissions collection (outside of model):

$userId = $user->id;
$premissions = Permissions::whereHas('users', function($userQuery) use ($userID) {
        $userQuery->where('id',$userId);
    })
    ->get();

Or do the flatmap:

$result = Roles::with('permissions')
    ->whereHas('users', function($userQuery) use ($userID) {
        $userQuery->where('id',$userId);
    })
    ->get()
    ->flatMap(function ($role) {
        return $role->permissions
    });
N69S
  • 16,110
  • 3
  • 22
  • 36
  • Thanks @N69S, I will try your solution. – leo95batista Aug 09 '21 at 22:03
  • Hello, I regret not having responded before, I have been trying to adjust your answer to my needs for several days .. but unfortunately .. I have not been able to. It happens that I am trying to optimize the queries that will be made to the DB. With the answer you offer me, several queries are executed that from my point of view are redundant. I would really like to know if there is a way to use the ```loadMissing``` method without affecting the original collection. Thanks for your time. – leo95batista Aug 16 '21 at 01:23
  • @leo95batista well, if you dont add the redundancy to your question, we cant help you with it. – N69S Aug 16 '21 at 05:21