Laravel One to One Polymorphic Relationship
A multi user authentication system example

An avid fullstack developer, previously trained on .Net and Java technologies. These days you'll find me hacking mainly with PHP & Javascirpt using Laravel and Vue.js, With a bit of interest in DevOps.
Authentication system is one of the most common and important features in software systems. Multi user authentication systems are also very common, where a software requires more than one user type, each with unique functionalities and access level. This could be found in e-commerce and inventory management systems. These systems mostly require user types like; admins, customers, cashiers-you name it.
The typical thing i use to do when modelling a multi user authentication systems is use a single User table with columns: id, first_name, last_name, email, password, and either user_type or is_admin to distinguish user types during authentication.
Let’s take an example of a school management system that requires single login page for Admin, Teacher, and Student. The common approach is to have a user table and then a profile table to store profile details. This can be inefficient as the data required from a student is differs from that of a teacher.
So, the polymorphic way of getting this done in Laravel is to have these models User, Admin, Teacher & Student. Below is the database table structure for all models:
users
id - integer
email - integer
password - string
userable_type - string
userable - id
admins
id - integer
first_name - string
last_name - string
middle_name - string
address - text
phone_number
profile_image_url - string
...
teachers
id - integer
first_name - string
last_name - string
staff_number - string
middle_name - string
date_of_birth - string
phone_number - string
gender - string
address - text
profile_image_url - string
department_id - integer
...
students
id - integer
first_name - string
last_name - string
middle_name - string
date_of_birth - string
gender - string
phone_number - string
address - text
profile_image_url - string
class_id - integer
registration_number - string
...
The User table migration can be defined like this in Laravel:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->integer('status')->default(1);
$table->string('userable_type');
$table->bigInteger('userable_id');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};
The above snippet was copied from a simple School Management Software project I worked on some years ago: MySMS. The rest of the migrations can be defined the usual way.
Instead of these lines below in the above given migration example:
$table->string('userable_type');
$table->integer('userable_id');
you could also write it like below and the appropriate columns, userable_type & userable_id would be generated automatically by Laravel when you run the migration:
$table->morphTo('userable');
The userable_id column is the foreign key column for either one of the tables: admins, teachers, or students table. while the user_type stores a string reference of the model class like: App\Models\Admin or App\Model\Teacher…This means a user can be an admin, teacher or a student.
The relationship can be defined like below, in the User Model:
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
/**
* Get the admin, teacher, or student relationships.
*/
public function userable(): MorphTo
{
return $this->morphTo();
}
/**
* Defines the dashbord/url for redirection after authentication
**/
public function getRedirectRoute()
{
return match($this->userable_type) {
'App\Models\Admin' => '/dashboard',
'App\Models\Teacher' => '/teacher/home',
'App\Models\Student' => '/student/home'
};
}
}
The function getRedirectRoute is a bonus and could be used like below in the authentication implementation:
NOTE: This was using Laravel Breeze’s authentication in Laravel 10.
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(auth()->user()->getRedirectRoute());
}
If you revisit what Polymorphism is in Object Oriented Programming, you would realise the role it plays here. The User entity is the main Object that can exist in many forms. either as an Admin, Teacher or a Student.
With this implementation, whenever the requirement changes and you are to introduce a new user type to the system, then all you need to do is to create a new model. for example, Staff with different data from the others.
In my opinion, this also follows SOLID principles. The Open/Close Principle, which states that “software entities should be open for extension, but closed for modification“. This makes your authentication system evolutionary.
I hope this gives you a clear understanding of One to One polymorphic relationship in Laravel and its applicability in your work or personal projects. This is a link to a repo that contains the full implementation MySMS App. In the next blogpost we will be looking at a more advance authentication system using Many to Many Polymorphic Relationship.


