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

Add withDefault() support to MorphTo relationships #24725

Closed
vpratfr opened this issue Jul 2, 2018 · 7 comments
Closed

Add withDefault() support to MorphTo relationships #24725

vpratfr opened this issue Jul 2, 2018 · 7 comments
Labels

Comments

@vpratfr
Copy link
Contributor

vpratfr commented Jul 2, 2018

Hi,

I am the person who originially opened #24055.

This is related to the fix in and its tests in #24061 which are not correct (unless I am wrong).

I fail to understand how calling ->withDefault() could determine how to create the related model instance.

According to me, this should throw an exception because (if I follow the example used in the docs) the Comment class has no idea what to create as default (a Video or a Post?)

So the only valid use case of withDefault for MorphTo is when you provide a dynamic callback.

For instance:

FooModality extends Model

BarModality extends Model

Mission extends Model  
    has attributes : 'template', 'modality_type', 'modality_id'

    public function modality() : MorphTo {
        return $this->morphTo('modality')
                ->withDefault(function() {
                    // Eloquent needs to update our 'modality_type', 'modality_id' columns
                    if ($this->template==='foo') return new FooModality();
                    if ($this->template==='bar') return new BarModality();

                    throw new IllegalArgumentException("Mission type is not handled");
                });
    }

If we want to be able to use withDefault() then we need to introduce somehow a mecanism to decide which class of related model to instanciate.

For instance a function with a given naming convention could be provided in the model which has the MorphTo relationship:

Mission extends Model

    // Function name is newDefault<<RelationshipName>>Instance
    protected function newDefaultModalityInstance() {
                    if ($this->template==='foo') return new FooModality();
                    if ($this->template==='bar') return new BarModality();

                    throw new IllegalArgumentException("Mission type is not handled");
    }

Then we could perfectly have withDefault() and withDefault([...]) as valid use cases because they are able to know which class to instanciate.

@vpratfr
Copy link
Contributor Author

vpratfr commented Jul 2, 2018

In addition to the previous message, it is also worth mentionning that the withDefault callback does not receive the appropriate parameter

Mission extends Model  
    has attributes : 'template', 'modality_type', 'modality_id'

    public function modality() : MorphTo {
        return $this->morphTo('modality')
                ->withDefault(function($model) {

/* 
 * $model is a new Mission instance with empty attributes
 * This is not consistent with other withDefault implementations where it would receive the instance
 * of the related model, not the relationship owner model.
 */

                });
    }

@umair321
Copy link

not working in Laravel 5.6

@bucaneer
Copy link
Contributor

bucaneer commented Feb 6, 2019

Since #27411 was merged, I believe this issue is resolved. You can specify the morph type attribute (modality_type in your example) to force a particular class to be used for the related instance. Any conditional logic you need to choose the appropriate class can be put in a dynamic attribute getter:

public function getModalityTypeAttribute($value = null) {
    if ($value === null) {
        if ($this->needsFoo()) return FooModality::class;
        if ($this->needsBar()) return BarModality::class;
    }
    return $value;
}

The callback passed to withDefault now receives the related model instance, so you can use it to customize the default attributes of the related model based on class, if you need to:

public function modality() {
    return $this->morphTo()
        ->withDefault(function($model) {
            if ($model instanceof FooModality) $model->foo_attribute = 'something';
            $model->general_attribute = 'something else';
        });
}

@taylorotwell
Copy link
Member

Your PR will be reverted. Causes failing tests on master.

@driesvints
Copy link
Member

I'll re-open this so I can have a look later at some point.

@driesvints driesvints reopened this Feb 7, 2019
@vpratfr
Copy link
Contributor Author

vpratfr commented Feb 7, 2019

Thanks.

I am struggling with how we are supposed to used the current code if that is meant to be sufficient to cover the needs for default morph related models.

Closest would be to use the received parent model in order to return a new instance of the morphed related model.

public function modality() {
    return $this->morphTo()->withDefault(function($parent) {
        if ($parent->needsFoo()) return new Foo();
        if ($parent->needsBar()) return new Bar();
        return null;
    });
}

In that case:

  • are we supposed to associate the new Foo or Bar models with the parent?
  • is that done by Eloquent automatically?

Seems a very different behaviour than withDefault when used on non-polymorphic relationships: those callbacks directly receive an instanciated and linked related model instance.

@taylorotwell
Copy link
Member

This was fixed by #27455

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants