skip to Main Content

I need help with my laravel 11 project. I have a table which acts as a morph table which would work with morphedByMany. But for this particular model I only need to retrieve one entry. My tables are: Site, Domain and then the intermidiate table: Siteable. Siteable defines the site_id, siteable_type and siteable_id columns. The following relationship defined at the Site model would retrieve all of my domains for the site, but a site should only have one domain:

public function domains(): MorphToMany
{
    return $this->morphedByMany(Domain::class, 'siteable');
}

I tried to change this to:

public function domain(): HasOneThrough
{
    return $this->hasOneThrough(Domain::class, Siteable::class, 'siteable_id', 'id', 'id');
}

Basically, a Site can have one domain, and as many of other models as it chooses, thus the following schema:

Schema::create('siteables', function (Blueprint $table) {
    $table->ulid('id')->primary()->index();
    $table->foreignUlid('site_id')->index()->constrained()->onUpdate('cascade')->onDelete('cascade');
    $table->ulidMorphs('siteable');
    $table->timestamps();

    // define indexes for performance
    $table->index(['site_id', 'siteable_type', 'siteable_id']);
});

What am i missing to retrieve one record?

UPDATE

SQL error I get after attempting morphOne:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'domains.siteable_id' in 'field list' (Connection: mysql, SQL: select `domains`.* from `domains` inner join (select MAX(`domains`.`id`) as `id_aggregate`, `domains`.`siteable_id`, `domains`.`siteable_type` from `domains` where `domains`.`siteable_id` in (01hy0zwf63wpt6t8c64q1388q9, 01hy0zxw8yb1nncrw7ytg50wwr) and `domains`.`siteable_type` = AppModelsSite group by `domains`.`siteable_id`, `domains`.`siteable_type`) as `latestOfMany` on `latestOfMany`.`id_aggregate` = `domains`.`id` and `latestOfMany`.`siteable_type` = `domains`.`siteable_type` and `latestOfMany`.`siteable_id` = `domains`.`siteable_id`).

2

Answers


  1. You could utilise a query scope for this that simply gets the latest domain id as a global select then eager loads the Domain using that id.

    public function latestDomain(): BelongsTo
    {
        return $this->belongsTo(Domain::class, 'latest_domain_id', 'id');
    }
    
    public function scopeWithLatestDomain($query): void
    {
        $query->addSelect(['latest_domain_id' => Domain::select('id')])
            ->whereColumn('siteables.id', 'domains.siteable_id')
            ->where('domains.siteable_type', static::class)
            ->latest()
            ->take(1)
            ->with(['latestDomain']);
    }
    

    This is untested and simply just a PoC but from what I can guage from your OP, it should be enough on its own. Usage:

    Siteable::withLatestDomain()
    
    Login or Signup to reply.
  2. If you want to fetch only one domain, you can use the hasOne relationship with a condition to ensure it only retrieves a Domain model. The hasOneThrough method you tried is not suitable for polymorphic relationships. Instead, you should define a custom query in your relationship.

    Define a custom hasOne relationship with a condition to only get the domain

    public function domain(): MorphOne
    {
        return $this->morphOne(Domain::class, 'siteable')->latestOfMany(); 
    }
    

    Verify Domain and Siteable models are set up to handle the polymorphic relationship properly:

    use IlluminateDatabaseEloquentRelationsMorphTo;
    
    class Domain extends Model
    {
        public function siteable(): MorphTo
        {
            return $this->morphTo();
        }
    }
    
    use IlluminateDatabaseEloquentRelationsMorphTo;
    
    class Siteable extends Model
    {
        public function siteable(): MorphTo
        {
            return $this->morphTo();
        }
    }
    

    now fetching the data,

    $site = Site::with('domain')->find($site_id);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search