One great feature of Eloquent is the possibility to have an entity whose relationship is polymorphic. The two parts of the word, poly and morphic, are from the Greek language. Since poly means many and morphic means shape, we can now easily imagine a relationship having multiple forms.
An amenity in our example software is something that is associated with a room, such as a Jacuzzi. Certain amenities, such as covered parking or an airport shuttle service, could also relate to an accommodation itself. We could create two pivot tables for this, one called amenity_room
and another called accommodation_amenity
. Another great way to do this is to combine the two into one table and use a field to distinguish between the two types or relationship.
To do this, we will need a field to distinguish between amenity and room and amenity and room, something we could call a relationship type. Laravel's Eloquent skillfully handles this automatically.
Eloquent uses the suffix -able
to make this happen. In our example, we would create a table that has the following fields:
id
name
description
amenitiable_id
amenitiable_type
The first three fields are familiar, but two new fields added. One of them will contain the ID of either the accommodation or the room.
For example, given a room with ID 5, amenitiable_id
will be 5
while amenitiable_type
will be Room
. Given an accommodation with ID 5, amenitiable_id
will be 5
while amenitiable_type
will be Accommodation
:
id |
name |
description |
amenitiable_id |
amenitiable_type |
---|---|---|---|---|
1 |
Wireless internet |
Internet conn. |
5 |
Room |
2 |
Covered parking |
Parking in garage |
5 |
Accommodation |
3 |
Sea view |
Ocean view from room |
5 |
Room |
In terms of code, the Amenity
model will now contain an "amenitiable" function:
<?php namespace MyCompanyAccommodation; use IlluminateDatabaseEloquentModel; class Amenity extends Model { public function rooms(){ return $this->belongsToMany('MyCompanyAccommodationRoom'), } public function amenitiable() { return $this->morphTo(); }
The Accommodation
model will change the amenities
method to use morphMany
instead of hasMany
:
<?php namespace MyCompany; use IlluminateDatabaseEloquentModel; class Accommodation extends Model { public function rooms(){ return $this->hasMany('MyCompanyAccommodationRoom'), } public function amenities() { return $this- >morphMany('MyCompanyAccommodationAmenity', 'amenitiable'), } }
The Room
model will contain the same morphMany
method:
<?php namespace MyCompanyAccommodation; use IlluminateDatabaseEloquentModel; class Room extends Model { protected $casts = ['room_number'=>'integer']; public function accommodation(){ return $this->belongsTo('MyCompanyAccommodation'), } public function amenities() { return $this- >morphMany('MyCompanyAccommodationAmenity', 'amenitiable'), } }
Now, when the amenities are requested for a room or an accommodation, Eloquent will automatically distinguish between them:
$accommodation->amenities(); $room->amenities();
Each of these functions returns the correct type of amenity for room and for accommodation.
It is possible, though, that some amenities could be shared between a room and an accommodation. In this case, a many-to-many polymorphic relation is used. The pivot table now adds several fields:
amenity_id |
amenitiable_id |
amenitiable_type |
---|---|---|
1 |
5 |
Room |
1 |
5 |
Accommodation |
2 |
5 |
Room |
2 |
5 |
Accommodation |
As illustrated, both the room with ID 5 and the accommodation with ID 5 have amenities with IDs 1 and 2.
If we would like to select all of the accommodations that are associated to a franchise, the has()
method is used, where the relation is passed as the parameter:
MyCompanyAccommodation::has('franchise')->get();
We will get the following JSON array:
[{"id":9,"name":"LovelyHotel","description":"Lovely Hotel Greater Pittsburgh","location_id":1,"created_at":null,"updated_at": "2015-03-13 22:00:23","deleted_at":null,"franchise_id":1}, {"id":12,"name": "Grand Hotel","description":"Grand Hotel Greater Cleveland","location_id":2,"created_at": "2015-02-0820:09:35","updated_at": "2015-02-0820:09:35","deleted_at":null,"franchise_id":1}]
Notice that the franchise_id
value is 1, which means the accommodations have a franchise associated with them. Optionally, a where
may be added to the has
creating a whereHas
function. The code is as follows:
MyCompanyAccommodation::whereHas('franchise', function($query){ $query->where('description','like','%Pittsburgh%'), })->get();
Notice that whereHas
takes a closure as its second parameter.
This would return only the accommodations where the description contains Pittsburgh
, so the returned array would contain only results like this:
[{"id":9,"name":"LovelyHotel","description":"Lovely Hotel Greater Pittsburgh","location_id":1,"created_at":null,"updated_at": "2015-03-13 22:00:23","deleted_at":null,"franchise_id":1}]
Another great mechanism that Eloquent provides is eager loading. If we want return all of the franchises together with all of their accommodations, we simply need to add an accommodations
function to our Franchise
model as follows:
public function accommodations() { return $this->hasMany('MyCompanyAccommodation'), }
Then, by adding a with
clause to the statement, the accommodations are returned for each franchise:
MyCompanyFranchise::with('accommodations')->get();
We can also list the rooms associated with each accommodation as follows:
MyCompanyFranchise::with('accommodations','rooms')->get();
If we want to return the rooms nested inside of the accommodation array, then the following syntax should be used:
MyCompanyFranchise::with('accommodations','accommodations.rooms') ->get();
We will get the following output:
[{"id":1,"accommodations": [ {"id":9, "name":"Lovely Hotel", "description":"Lovely Hotel Greater Pittsburgh", "location_id":1, "created_at":null, "updated_at":"2015-03-13 22:00:23", "deleted_at":null, "franchise_id":1, "rooms":[{"id":1,"room_number":0,"created_at":null,"updated_at": null,"deleted_at":null,"accommodation_id":9}, ]}, {"id":12,"name":"GrandHotel","description":"Grand Hotel Greater Cleveland","location_id":2,"created_at":"2015-02-08…
In this example, rooms
is contained within accommodation
.