简体   繁体   中英

Laravel - reverse polymorphic relationship

I basically have two models ( dog and cat ) of the same type: pets . The table pets joins all dogs and cats in the database. Now I would like to be able to find a specific pet through the pet id in the PetController. Like so:

$pet = Pet::findOrFail($id); // returns a dog or cat

Tables structure:

┌──────────────┐  ┌───────────┐  ┌───────────┐
│ pets         │  │ dogs      │  │ cats      │
├──────────────┤  ├───────────┤  ├───────────┤
│ id           │  │ id        │  │ id        │
│ related_type │  │ name      │  │ name      │
│ related_id   │  │ eye_color │  │ tail_size │
└──────────────┘  └───────────┘  └───────────┘

Pets table:

┌────┬──────────────┬────────────┐
│ id │ related_type │ related_id │
├────┼──────────────┼────────────┤
│ 1  │ dog          │ 1          │
├────┼──────────────┼────────────┤
│ 2  │ dog          │ 2          │
├────┼──────────────┼────────────┤
│ 3  │ cat          │ 1          │
└────┴──────────────┴────────────┘

I have searched the Laravel docs but none of the relationships seem to fit for this problem. Only the polymorphic relationship would work the other way around, so that I could access the pet model through the dog- or cat-id. But I am looking for a solution that work the other way around. Is there any kind of relationship without needing to use nasty if-else in the PetController manually?

Thank you!

You can define a polymorphic relationship between these 3 models like this

Pet Model

public function related(){
     $this->morphTo();
}

Dog Model

public function pets(){
     $this->morphMany('App\Pet', 'related');
}

Cat Model

public function pets(){
     $this->morphMany('App\Pet', 'related');
}

Now fetch it like this

$pet = Pet::findOrFail($id)->related;
dd($pet); //you will get either cat or dog

Easy create

$dog = Dog::create(['name'=> 'dog1', 'eye_color' => 'gray']);
$dog->pets()->create();

Check details here https://laravel.com/docs/5.6/eloquent-relationships#polymorphic-relations

You need to keep Model namespase in pats table( related_type column). Add to your PetModel

public function concretePet()
{
    return $this->hasOne($this->related_type, 'id', 'related_id');
}

Use:

$pet = Pet::findOrFail($id)->concretePet;

You can create your own trait for this:

app/MorphToModel.php

<?php

namespace App;

trait MorphToModel
{
    protected function morphToModel($related, $name = 'related', $foreignKey = 'id')
    {
        $table = $this->getTable();

        return $this->belongsTo($related, $name . '_id', $foreignKey)
            ->join(
                $table,
                $table . '.' . $name . '_id',
                ($model = new $related)->getTable() . '.' . $model->getKeyName()
            )->where($table . '.' . $name . '_type', $related);
    }
}

Use the trait in your model(s):

app/Pet.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Pet extends Model
{
    use MorphToModel;

    // ...

    public function related()
    {
        return $this->morphTo();
    }

    public function cat()
    {
        return $this->morphToModel(Cat::class);
    }

    public function dog()
    {
        return $this->morphToModel(Dog::class);
    }
}

Usage:

$pet = Pet::findOrFail($id);

$pet->cat; // A cat model or null.

$pet->dog; // A dog model or null.

$pet->cat() // A cat relationship query builder.

$pet->dog() // A dog relationship query builder.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM