Laravel Eloquent polymorphic relations are similar to standard relationships, except that the model on one side of the relationship is defined by a column in the database table. Typically this would allow a single ‘child’ model to be attached to multiple different parents, for example, a Comment model can belong to a Post or a Page. An Image model can belong equally to an Item or a Category. In this example we are going to reverse this typical arrangement and have the User model belong to different profile types.
belongsTo
relationship where in a table contains a foreign key to the ID of the other model, a polymorphic relationship uses an additional column to specify the model also.In this example, we will be able to create tables (and models) that contain profile information for different types of users. The Admin user has one set of attributes and a Customer has a totally different set of attributes. Both types still use the User model for identification and access to our application.
Method
Assuming a simple Laravel installation, where php artisan make:auth
has been used to create a basic authentication solution.
We can add an AdminProfile and CustomerProfile
php artisan make:model AdminProfile -m
php artisan make:model CustomerProfile -m
(the -m will create migrations as well as the model)
In the migration files, add the attributes that are specific to that type of user
Schema::create('admin_profiles',function(Blueprint$table){ $table->increments('id'); $table->string('department')->nullable(); $table->string('payroll')->nullable(); $table->string('manager')->nullable(); $table->timestamps(); });
Schema::create('customer_profiles', function (Blueprint $table) { $table->increments('id'); $table->string('address1')->nullable(); $table->string('address2')->nullable(); $table->string('address_city')->nullable(); $table->string('address_postcode')->nullable(); $table->string('address_country')->nullable(); $table->string('mobile')->nullable(); $table->string('landline')->nullable(); $table->timestamps(); });
The only other database work to do is to add the polymorphic columns to the existing users table. We can create a new migration to extend the users.
php artisan make:migration add_polymorphism_to_users --table=users
Schema::table('users', function (Blueprint $table) { $table->string('profile_type')->nullable(); $table->unsignedInteger('profile_id')->nullable(); });
A little work on the models so that they have the polymorphic relationship
User Model
Add to the existing user model;
protected $with = ['profile']; public function profile() { return $this->morphTo(); }
AdminProfile Model
<?php namespace App; use Illuminate\Database\Eloquent\Model; class AdminProfile extends Model { protected $guarded = []; public function user() { return $this->morphOne('App\User', 'profile'); } }
CustomerProfile Model
<?php namespace App; use Illuminate\Database\Eloquent\Model; class CustomerProfile extends Model { protected $guarded = []; public function user() { return $this->morphOne('App\User', 'profile'); } }
Run the migrations php artisan migrate
We can now have two different profile types associated with any user. Two users have been created, and they have no profile associated with them at the moment.
Let’s test it with Tinker;
$profile = App\AdminProfile::create(['manager'=>'Donald','department'=>'Retail'])
$profile->user()->save(User::find(1))
We now have created a profile for our administrator user and attached it to their user account
$profile = App\CustomerProfile::create(['address1'=>'Lilac Cottage','address2'=>'Leeming Lane'])
$profile->user()->save(User::find(2))
And created a customer profile and attached this user 2
We can check the database and see the profile_type column has been populated
Thanks to the $with
attribute added to the user model, whenever we get a user, we will also get the profile fields
>>> User::find(1) => App\User {#2962 id: 1, name: "john doe", email: "john@mycorp.com", email_verified_at: null, created_at: "2019-01-19 13:00:23", updated_at: "2019-01-19 13:06:06", profile_type: "App\AdminProfile", profile_id: 1, profile: App\AdminProfile {#2972 id: 1, department: "Retail", payroll: null, manager: "Donald", created_at: "2019-01-19 13:05:39", updated_at: "2019-01-19 13:05:39", }, }
>>> User::find(2) => App\User {#2966 id: 2, name: "jane smith", email: "jane@hotmail.com", email_verified_at: null, created_at: "2019-01-19 13:00:54", updated_at: "2019-01-19 13:12:12", profile_type: "App\CustomerProfile", profile_id: 1, profile: App\CustomerProfile {#2980 id: 1, address1: "Lilac Cottage", address2: "Leeming Lane", address_city: null, address_postcode: null, address_country: null, mobile: null, landline: null, created_at: "2019-01-19 13:12:02", updated_at: "2019-01-19 13:12:02", }, }
Bonus
To make it easy to know what type of user profile we are dealing with, we can extend the User model with a couple of accessors
public function getHasAdminProfileAttribute() { return $this->profile_type == 'App\AdminProfile'; } public function getHasCustomerProfileAttribute() { return $this->profile_type == 'App\CustomerProfile'; }
>>> User::find(2)->hasAdminProfile
=> false
>>> User::find(2)->hasCustomerProfile
=> true
>>>
Hi,
I like to know how perform a simple query with all user with CustomerProfile for example?
I’ll answer assuming you mean that you want to get all Customers from the user model
$customers = User::where(‘profile_type’,’App\Customer’)->get();
You could put this where statement in a Scope and then when needed
$customers = User::customers()->get();
Hi Mark!
I saw your comments on Laracast about this topic.
Thanks, it helped me.
But, I have one more case, maybe you could help me with advice.
I create the following structure for the job search portal:
User – single entry point
Applicant – morphOne
Employer – morphOne. But it can also add/register several users to manage company vacancies, they are assigned an email and a password with which they can log in to the application.
How do I properly implement the relationship between employer managers and users?
Hi Eldar. I guess I would think about some form of Account object. The manager can belong to the account, as can the Employee Users. The Account model could have a manager_id field? Sounds like you need to start considering role based permissions like an RBAC solution?
Thanks for answer!
Could you give an example on models please?
Nice post!!!
What if we need to have both customer and admin profile associated to one user? It will work with a many to many polymorphic relationship?
Sorry, the solution as posted only stores one related profile
Thanks for good example, this article really helped me.
how to make sure all user’s CustomerProfile and AdminProfile has unique id?
Each model uses auto-incrementing ID. They don’t need to be the same across the models so each will just take the next ID. There is no complication ?
Thanks so much for this post. I read thru the Laravel documents and it wasn’t making any sense (ended up being backwards for what I needed) and I didn’t see anything in the documentation about the morphOne method which turned out to work perfectly.
Hey there, thank you for this article How would you go about deleting related models ? thank you
You would need to delete it within the controller responsible for deleting the user since you cannot use FK constraints since the database does not recognise the relationship
Hi, I am working on a new app and like the idea of using Polymorphic relationships for the different user types, but I am having a hard time working out the details on the user creation and then filling in the profile type and profile id fields. Do you have any code examples that show how this is done.
Ideally, I am looking for
Create a new user and select user types, click continue, and based on the user type and appropriate fields are available to fill out from the user type table. .
Thank you
Hi Daniel
When you have determined what type of user they are, you can show them the form that has the appropriate fields. Your form can have a different endpoint for the save, and in that controller, populate the fields for that profile type. Then attaching it to the user record can be as I show in the ‘test it with tinker’
$profile = App\AdminProfile::create(['manager'=>'Donald','department'=>'Retail'])
$profile->user()->save(User::find(1))
Each user type has a model for their profile. Just fill it in and save it, then attach the profile to the user with the second line above.
Does this help?
Thank you so much for the quick reply and the guidance!
Where I am running into a mental block, is how/when to assign the profile to the user account. Over the past few hours I created my Admin, Customer and Manager profile tables, created a form for each that has the Fields specific to them and then I have my main user registration form.
When I fill out the user registration form and select the profile type for the account I am creating, let’s say user type customer, how to I then associate the account I am creating with the fields that are going to show up on the next page with the customer-specific fields.
Or, alternatively, I have thought about making 3 separate registration forms one for admin, one for the manager, and one for the customer that has all the fields from the main user table and then the fields that are specific to the user type, but again, I just can’t seem to wrap my head around how to create the entry and set the relationship at the same time.
Thank you again for you help and guidance with this.
Think ahead to the user wanting to update their profile. Based on the type of user, you will show a different view, and have the form post on that view go to a controller method that can save it.
At registration this is no different. The only difference is that you create profile a,b or c rather than edit a,b or c
Does that make it clearer?
Personally I would create an empty profile for the user on signup and then send them to the appropriate edit profile route to fill in the rest of the details.
Thanks a lot for sharing this concept, it suits my needs perfectly!
I have Referral Code table that share unique code to members, team members and admin. each user must have only one unique referral code. When any member is invited through that referral code I want to link that member under it’s parent. How can I achieve it?
What I think is about having Referral modal which have member_id and parent_id (which could be Member, TeamMember, Admin)
Note : Only members can be invited.