- Users ask for a budget quotation for a certain task.
- Every task has a location.
- Professionals can subscribe to different zones.
- A zone can be a region or a city.
This article was peer reviewed by Christopher Vundi and Christopher Pitt. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
Scaffolding Application
I assume you have your development environment already set up. If not, you can check this Homestead Improved Quick Tip or just use the official Homestead Box. Go ahead and create a new Laravel project using the Laravel installer or via Composer.laravel new demo
Or
composer create-project --prefer-dist laravel/laravel demo
Edit your .env
file with your database credentials.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
Creating Migrations
Before creating our migrations, we should talk about Eloquent polymorphic relations and how they can benefit us in this case! Polymorphism is often used when an object can have different forms (shapes). In our case, professional users subscribe to different zones and they get notified whenever a new job is posted in this area. Lets start by creating thezones
table.
php artisan make:model Zone --migration
This creates a migration file, but we do need to add a bit of code to it before it’s complete, as demonstrated by:
// database/migrations/2016_12_02_130436_create_zones_table.php
class CreateZonesTable extends Migration
{
public function up()
{
Schema::create('zones', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->integer('zone_id')->unsigned();
$table->string('zone_type');
});
}
public function down()
{
Schema::dropIfExists('zones');
}
}
Next, we create the cities and regions tables.
php artisan make:model Region --migration
// database/migrations/2016_12_02_130701_create_regions_table.php
class CreateRegionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('regions', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('regions');
}
}
php artisan make:model City --migration
// database/migrations/2016_12_02_130709_create_cities_table.php
class CreateCitiesTable extends Migration
{
public function up()
{
Schema::create('cities', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 255);
$table->integer('postal_code')->unsigned();
$table->integer('region_id')->unsigned()->nullable();
});
}
public function down()
{
Schema::drop('cities');
}
}
We could’ve achieved the same result by making a many to many relation with the cities and regions table. However, the zones table will act as an abstract class for the two other tables. The zone_id
will hold an id for a city or a region and using the zone_type
value Eloquent will decide which model instance to return.
Creating Models
Eloquent has the ability to guess the related table fields, but I decided not to use it just to explain how to map database fields to models. I will explain them as we go along!// app/User.php
class User extends Authenticatable
{
// ...
public function cities()
{
return $this->morphedByMany(City::class, 'zone', 'zones', 'user_id', 'zone_id');
}
public function regions()
{
return $this->morphedByMany(Region::class, 'zone', 'zones', 'user_id', 'zone_id');
}
}
The morphedByMany
method is similar to the manyToMany
one. We specify the related model, the mapping field name (used for zone_type
and zone_id
), related table name, current model foreign key and the morphed relation key.
We could’ve automated this by letting Eloquent guess field names, if you take a look at the documentation you’ll see that we can name the fields as zoneable_id
and zoneable_type
, then only specify the mapped field name (return $this->morphedByMany(City::class, 'zoneable'
).
// app/Region.php
class Region extends Model
{
// ...
public function cities()
{
return $this->hasMany(City::class);
}
public function users()
{
return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
}
}
We can quickly guess the parameters definition from the above code. We specify the related table name, morphed relation type and ID.
// app/City.php
class City extends Model
{
// ...
public function users()
{
return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
}
}
Now that everything is set up, we can start testing our relations and see if everything is working as expected. We should first seed our tables to save some time. You can grab the database seeder’s code from the GitHub repository.
Using Relations
We can attach cities and regions to users using syncing, attaching, and detaching Eloquent methods. Here’s an example:$user = App\User::find(1);
$user->cities()->sync(App\City::limit(3)->get());
This will attach three cities to the selected user, and we can do the same for regions.
$user = App\User::find(1);
$user->regions()->sync(App\Region::limit(3)->get());
If we inspect our database now to see what was saved, we can see the following:
mysql> select * from zones;
+---------+---------+------------+
| user_id | zone_id | zone_type |
+---------+---------+------------+
| 1 | 1 | App\City |
| 1 | 2 | App\City |
| 1 | 3 | App\City |
| 1 | 1 | App\Region |
| 1 | 2 | App\Region |
| 1 | 3 | App\Region |
+---------+---------+------------+
6 rows in set (0.00 sec)
We can also detach regions if they exist.
$user = App\User::find(1);
$user->regions()->detach(App\Region::limit(3)->get());
Now, we can fetch user cities and regions as we would do on a normal many to many relationship:
$user = App\User::find(1);
dd($user->regions, $user->cities);
We could add a new method called zones
which will merge cities from regions with individual selected cities.
class User extends Authenticatable
{
// ...
public function zones()
{
return $this->regions->pluck("cities")->flatten()->merge($this->cities);
}
}
The pluck method will get a collection of cities from each region, which be then flattened (merge all the collections into one) and merged with the user selected cities. You can read more about collections in the documentation, and if you want to learn more, I recommend this Refactoring to Collections book from Adam Wathan.
Conclusion
Even though polymorphic relations are rarely used in applications, Eloquent makes it’s easy to deal with related tables and returning the correct data. If you have any questions or comments about Eloquent or Laravel in general, you can post them below and I’ll do my best to answer them!Frequently Asked Questions on Polymorphic Relations and Eloquent
What is the main difference between polymorphic and many-to-many relationships in Laravel?
In Laravel, both polymorphic and many-to-many relationships are types of Eloquent relationships. However, they differ in their functionality. A many-to-many relationship is used when multiple records in one table are associated with multiple records in another table. On the other hand, a polymorphic relationship allows a model to belong to any number of models on a single association. It provides more flexibility as it allows you to have a single list of unique entities that can be used in different contexts.
How can I define a polymorphic relationship in Laravel?
Defining a polymorphic relationship in Laravel involves a few steps. First, you need to define the relationship in the model using the morphTo method. This method will automatically determine the type and id columns. Then, you need to define the inverse of the relationship using the morphMany or morphOne methods. These methods accept three arguments: the related model, the name of the polymorphic type, and the foreign key name.
Can you provide an example of a polymorphic relationship in Laravel?
Sure, let’s consider a scenario where you have three models: Post, Video, and Comment. Both Post and Video models can have comments, but the type of commentable model can be different. In this case, you can use a polymorphic relationship. The Comment model would have a commentable_id and commentable_type field, which will store the ID and class name of the associated model respectively.
What are the benefits of using polymorphic relationships in Laravel?
Polymorphic relationships in Laravel provide a flexible and efficient way to handle related data. They allow a model to belong to more than one other model on a single association. This means you can have a single list of entities that can be used in different contexts, reducing the need for duplicate code. They also make your code more maintainable and easier to understand.
How can I retrieve records using a polymorphic relationship in Laravel?
Retrieving records using a polymorphic relationship in Laravel is straightforward. You can use the name of the method that defines the relationship to retrieve the related records. For example, if you have a Comment model with a polymorphic relationship to a Post model, you can retrieve all comments for a post using the comments method: $post->comments.
How can I delete records using a polymorphic relationship in Laravel?
Deleting records using a polymorphic relationship in Laravel is similar to retrieving records. You can use the name of the method that defines the relationship to delete the related records. For example, if you have a Comment model with a polymorphic relationship to a Post model, you can delete all comments for a post using the comments method: $post->comments()->delete().
Can I use polymorphic relationships with Laravel’s query builder?
Yes, you can use polymorphic relationships with Laravel’s query builder. However, keep in mind that the query builder does not provide any support for retrieving related records. You will need to manually join the related tables and select the appropriate columns.
How can I use polymorphic relationships with Laravel’s Eloquent ORM?
Using polymorphic relationships with Laravel’s Eloquent ORM is straightforward. You can define the relationship in your model using the morphTo, morphMany, or morphOne methods. Then, you can use these methods to retrieve or manipulate the related records.
Can I use polymorphic relationships with Laravel’s collections?
Yes, you can use polymorphic relationships with Laravel’s collections. Collections provide a convenient wrapper for working with arrays of data, and they work seamlessly with Eloquent’s relationships. You can use the pluck, map, filter, and other collection methods to work with the related records.
How can I handle polymorphic relationships in Laravel when dealing with complex data structures?
When dealing with complex data structures, it’s important to carefully plan your database schema and Eloquent relationships. Polymorphic relationships can be a powerful tool in these situations, as they allow a model to belong to more than one other model on a single association. However, they can also add complexity to your code, so it’s important to use them judiciously.
Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.