designerandgeek's avatar

Eloquent: Various models may have only one Icon

I'm struggling with Eloquent relationships. I have an Icon model that I want to be able to associate with different other models (for example, Link – a model with links to social media etc.).

One icon may be attached to many different models, but each model may only have one icon.

Therefore, a MorphOne relationship doesn't work, because that limits attachment of an icon to one model only.

And a MorphToMany relationship allows many icons to be attached to each model.

How do I set this up correctly?

0 likes
4 replies
hupp's avatar

@designerandgeek As i understand you have Icon Model and Link Model and OtherModel like Link. For all model you can use icon model as common. Try bellow relationship with it.

class Icon extends Model
{
    public function iconable()
    {
        return $this->morphTo();
    }
}

class Link extends Model
{
    public function icon()
    {
        return $this->morphOne(Icon::class, 'iconable');
    }
}

class AnotherModel extends Model
{
    public function icon()
    {
        return $this->morphOne(Icon::class, 'iconable');
    }
}
// ... and so on for each model
designerandgeek's avatar

No, as mentioned in my original post, MorphOne doesn't work because it limits each icon to be attached to one model only. From my test:

SQLSTATE[HY000]: General error: 1 no such column: icons.iconable_type (Connection: sqlite, SQL: select * from "icons" where "icons"."iconable_type" = App\Models\Link and "icons"."iconable_id" = 1 and "icons"."iconable_id" is not null limit 1)

From the icon's point of view, it needs a pivot table, while from the iconable models' point of view it needs to be limited to only one icon, so it might as well just have an icon_id column …

Hmmm … Maybe this isn't a relationship at all, come to think of it. I mean, other than the fact that a model may or may not have an icon.

designerandgeek's avatar
Level 8

Yep, here's what I ended up with:

class Link extends Model
{
    use HasFactory;

    protected $fillable = [
        'slug',
        'title',
        'url',
        'icon_id',
    ];

    public function icon() : HasOne
    {
        return $this->hasOne(Icon::class, 'id', 'icon_id');
    }
}
LaryAI's avatar
Level 58

To achieve the desired functionality where various models may have only one icon, but one icon can be associated with multiple models, you can use a polymorphic one-to-many relationship in Laravel's Eloquent. This setup allows a single icon to be associated with multiple models, but each of those models can only have one icon at a time.

Here's how you can set it up:

  1. Icon Model: This will be the model that can belong to various other models.
class Icon extends Model
{
    public function iconable()
    {
        return $this->morphTo();
    }
}
  1. Other Models (e.g., Link): Each model that can have an icon should define a morphOne relationship.
class Link extends Model
{
    public function icon()
    {
        return $this->morphOne(Icon::class, 'iconable');
    }
}
  1. Database Migration for Icons Table: You need to set up the icons table to store the ID and type of the related model.
Schema::create('icons', function (Blueprint $table) {
    $table->id();
    $table->string('url'); // Assuming you store URL of the icon
    $table->morphs('iconable'); // This adds `iconable_id` and `iconable_type`
    $table->timestamps();
});
  1. Usage: Associating an icon with a model and retrieving it.

To associate an icon with a model:

$link = Link::find(1);
$icon = new Icon(['url' => 'path/to/icon.png']);
$link->icon()->save($icon);

To retrieve the icon for a model:

$icon = $link->icon;

This setup ensures that each model like Link can have only one Icon, but the Icon can be associated with multiple models (not simultaneously with the same instance, but one icon entry per model). If you need to allow an icon to be reused across different instances or types of models without creating new icon entries, you would need a different approach, potentially involving a many-to-many relationship with additional logic to enforce single association per model instance.

Please or to participate in this conversation.