Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Conversation

salkhwlani
Copy link
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
Copy link
Contributor

sebdesign commented Aug 29, 2018

@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