Skip to content

[5.6] access to parent model from withDefault closure#23334

Merged
taylorotwell merged 1 commit into
laravel:5.6from
salkhwlani:feature/access_to_parent_model_from_withDefault_56
Feb 28, 2018
Merged

[5.6] access to parent model from withDefault closure#23334
taylorotwell merged 1 commit into
laravel:5.6from
salkhwlani:feature/access_to_parent_model_from_withDefault_56

Conversation

@salkhwlani

Copy link
Copy Markdown
Contributor

Sometimes, you may wish to return default value of relations using advanced logic and condition base of parent values

This already support but not work for lazy eager loading for example

// Message model
public function user()
{
        return $this->belongsTo(User::class)->withDefault(function ($user) {
            $user->name = $this->getAttribute('username');
            $user->email = $this->getAttribute('email');

            return $user;
        });
}

// single record.
$message = Message::first();
$message->user->name; // return username from message model as except.

// but i we try access to user info use lazy eager loading
$messages = Message::with('user')->get();
$messages->first()->user->name; // return null

This PR support pass parent model to closure to make sure the parent model available in all cases.

public function user()
{
        return $this->belongsTo(User::class)->withDefault(function ($user, $parent) {
            $user->name = $parent->getAttribute('username');
            $user->email = $parent->getAttribute('email');

            return $user;
        });
}

thanks

Sometimes, you may wish to return default value of relations using advanced logic and condition base of parent values

This already support but not work for lazy eager loading for example

```php
// Message model
public function user()
{
        return $this->belongsTo(User::class)->withDefault(function ($user) {
            $user->name = $this->getAttribute('username');
            $user->email = $this->getAttribute('email');

            return $user;
        });
}

// single record.
$message = Message::first();
$message->user->name; // return username from message model as except.

// but i we try access to user info use lazy eager loading
$messages = Message::with('user')->get();
$messages->first()->user->name; // return null
```

This PR support pass parent model to closure to make sure the parent model available in all cases.

```php
public function user()
{
        return $this->belongsTo(User::class)->withDefault(function ($user, $parent) {
            $user->name = $parent->getAttribute('username');
            $user->email = $parent->getAttribute('email');

            return $user;
        });
}
```

thanks
@taylorotwell taylorotwell merged commit 9ac4664 into laravel:5.6 Feb 28, 2018
@salkhwlani salkhwlani deleted the feature/access_to_parent_model_from_withDefault_56 branch February 28, 2018 13:50
@sebdesign

sebdesign commented Aug 29, 2018

Copy link
Copy Markdown
Contributor

@taylorotwell is there any chance we can backport this to Laravel 5.5? This PR is actually a workaround for a bug, and not a new feature.

I have a HasOne relationship with a withDefault callback, which is accessing the parent model using $this. This is working fine when I use the relationship on a single model.

But when I'm eager loading this relation from an eloquent collection, all the results are wrong.
The load method in the eloquent collection is using the first() item to eager-load the relationships,
so in the withDefault callback, $this is bound to the first item in the collection, which is returning wrong results every time the withDefault callback is called.

public function salary()
{
    return $this->hasOne(Salary::class)->withDefault(function ($salary) {
        return $salary->fill([
            'currency' => $this->currency,
        ]);
    });

It works fine on single models:

$userA = User::create(['currency' => 'USD']);
$userA->salary()->create(['amount' => 100, 'currency' => $userA->currency]);
$userA->salary->currency === 'USD'; // this is true

$userB = User::create(['currency' => 'EUR']);
$userB->salary->currency === 'EUR'; // this is true

But when we eager-load eloquent collections, things are getting messy:

$users = User::get()->load('salary');

$users->find($userA)->salary->currency === 'USD'; // this is true

// Here the salary currency is USD, because when we used `$this->currency` in the withDefault callback,
// `$this` was bound to the $userA, and not the matching parent user instance.
$users->find($userB)->salary->currency === 'EUR'; // this is false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants