Search code examples
laraveleloquentlaravel-relations

Laravel Parties relationship


Goodmorning everyone, I'm literally losing my mind over an entity relationship issue with Laravel. I'm using the latest version of Laravel, 8.x. My data model is composed as follows.

Parties Table

  1. id
  2. party_details_id
  3. party_type

People Table

  1. id
  2. party_type ('P')
  3. name
  4. surname

Companies Table

  1. id
  2. party_type ('C')
  3. name
  4. surname

As you can see from the example, "Parties" is the table that contains all parties, both people and companies. The connection key is double, ie the pair formed by "party_details_id" and "party_type". In the "Parties" table there can be two equal values ​​in the "party_details_id" column but there can only be one value with "party_details_id" and "party_type". I am having enormous difficulty setting up relationships on Laravel. Reading a few articles I came to this situation:

Parties Model

public function companies()
    {
        return $this->hasMany(Companies::class, 'id', 'party_details_id')->where('party_type', 'C');
    }

    public function people()
    {
        return $this->hasOne(People::class, 'id', 'party_details_id')->where('party_type', 'P');
    }

People Model

public function party()
    {
        return $this->belongsTo(Parties::class, 'party_details_id')->where('party_type', 'P');
    }

Companies Model

public function party()
    {
        return $this->belongsTo(Parties::class, 'party_details_id', 'id')->where('party_type', 'C');
    }

In a test route I inserted this code:

Route::get('/people', function () {
    $people = new People();
    return $people->with('party')->get()->toArray();
});

What i get is:

[
  {
    "id": 1,
    "party_type": "P",
    "name": "Alex",
    "surname": "test",
    "created_at": "2021-09-20T20:14:07.000000Z",
    "updated_at": null,
    "party": null
  }
]

As you can see from the answer, "party" is empty. Why? Looking from Telescope at the query that is launched I see this:

select * from `parties` where `party_type` = 'P' and 0 = 1

What am I doing wrong? Can you help me manage this relationship? Thanks


Solution

  • I believe you mean the Party can either belong to the People model or the Company model, and not both. If this is true, then use a polymorphic One to Many relationship.

    Laravel Docs: https://laravel.com/docs/8.x/eloquent-relationships#one-to-many-polymorphic-relations

    Company

    Note: the protected $with attribute makes sure the Party always loads with the object.

    class Company extends Model
    {
        protected $with = ['party'];
    
        public function party()
        {
            return $this->morphMany(Party::class,'object');
        }
    }
    

    People Model

    Note: the protected $with attribute makes sure the Party always loads with the object.

    class People extends Model
    {
        protected $with = ['party'];
    
        public function party()
        {
            return $this->morphMany(Party::class,'object');
        }
    }
    

    Party Model

    You would then have an 'object' relationship on the Party model so that $party->object get's either the People or the Company. I recommend using the 'object' relationship instead of the 'people' and 'company' relationships since all models would have either one or the other, but I included both here in case you wanted it.

    class Party extends Model
    {
        public function object()
        {
            return $this->morphTo();
        }
    
        public function people()
        {
            return $this->belongsTo(People::class,'object_id')->where('object_type','=',People::class);
        }
    
        public function company()
        {
            return $this->belongsTo(Company::class,'object_id')->where('object_type','=',Company::class);
        }
    }
    

    Now you can use $party->object, or $party->people and $party->company to get the Person or Company it belongs to.

    $party->object; // Either Company or People object
    $party->people; // People object, would fail if the party belongs to Company
    $party->company; // Company object, would fail if the party belongs to People 
    

    Now when you get the People or Company object, it should include the Party automatically.

    Route::get('/people', function () {
        $people = new People();
        return $people->get()->toArray();
    });
    

    As noted above, the $with attributes ensures the party is loaded each time automatically. If you'd prefer to not load the party every time, remove the $with attribute and load it after.

    Route::get('/people', function () {
        $people = new People();
        return $people->with('party')->get()->toArray();
    });