Polymorphic many-to-many relationship model property doesn't return collection but ->get() does

31 Views Asked by At

I have two models Product and ProductType which both have a relationship with the Usp model. I have set up the models like this.

class Product extends Model
{
    use HasFactory, SoftDeletes;

    protected $guarded = [];

    public function usps(): MorphToMany
    {
        return $this->morphToMany(Usp::class, 'uspable');
    }
}


class ProductType extends Model
{
    use HasFactory, SoftDeletes;

    protected $guarded = [];

    public function usps(): MorphToMany
    {
        return $this->morphToMany(Usp::class, 'uspable');
    }
}

class Usp extends Model
{
    use HasFactory, SoftDeletes;

    protected $guarded = [];

    public function products(): MorphToMany
    {
        return $this->morphedByMany(Product::class, 'uspable');
    }

    public function productTypes(): MorphToMany
    {
        return $this->morphedByMany(ProductType::class, 'uspable');
    }
}

As far as I can see I have everything setup correctly but for some strange reason, when I call the property of the ProductType model it returns null (i.e. ProductType::find(1)->usps). But when I call the Query Builder method ProductType::find(1)->usps()->get() it gives me the correct results. For the Product model both ->usps and ->usps()->get() seem to work just fine. I have no idea what I am doing wrong here.

2

There are 2 best solutions below

1
Mukesh Khatri On

It looks like you have set up your relationships correctly, but there might be an issue with eager loading. By default, relationships are not loaded until you explicitly request them. When you use ->usps, it may not be eager loading the relationship, resulting in null. However, when you use ->usps()->get(), you are explicitly triggering the loading of the relationship.

To ensure that the relationship is loaded when you access it directly, you can use eager loading with the with method. Try modifying your code as follows:

$productType = ProductType::with('usps')->find(1);
$usps = $productType->usps; // This should now work as expected

By using with('usps'), you are instructing Laravel to eager load the "usps" relationship along with the "ProductType" model, and then you can access it directly without having to call ->usps()->get().

Alternatively, you can use the load method after retrieving the ProductType instance:

$productType = ProductType::find(1);
$productType->load('usps');
$usps = $productType->usps; // This should now work as expected

This should resolve the issue of returning null when accessing the usps relationship directly on the ProductType model.

0
The Stompiest On

I fixed it by creating a trait called HasUsps and defining the builder method and custom attribute getter in it. and implemting the trait to every model that has a polymorphic relation with the Usp model. It still feels like a workaround for something that should just work, but I have no idea where the 'real' problem lays. So for now this is fine.

trait HasUsps
{
    public function usps(): MorphToMany
    {
        return $this->morphToMany(Usp::class, 'uspable');
    }

    public function getUspsAttribute(): Collection
    {
        return $this->usps()->get();
    }
}