I needed a better way of handling different model types based on a single database table.
The example I had was that there was a settings table for the application that can return different types of settings based on the type column of the row.
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->text('settings');
$table->timestamps();
});
The type column would be used to determine the type of setting that was being stored in the settings column. Which
can be something like
email, slack, sms etc. The settings column would be a JSON column that would store the settings for the
notification type such as the email to send
or the token and channel to send the slack notification.
Because the value of the settings column can consist of different types of settings I needed a way to handle this in the
application. I wanted different classes
to use for each type of setting, such as EmailSetting, SlackSetting, SmsSetting etc.
The EmailSetting will have a method getEmail to get the email in the settings json column.
<?php
namespace App\Models\Notifications;
use Illuminate\Database\Eloquent\Model;
class EmailSetting extends Model
{
public function getEmail()
{
return $this->settings['email'] ?? '';
}
}
While the SlackSetting class will have a method getChannel to get the channel in the settings json column and
a getToken to get the API token.
<?php
namespace App\Models\Notifications;
use Illuminate\Database\Eloquent\Model;
class SlackSetting extends Model
{
public function getChannel()
{
return $this->settings['channel'] ?? '';
}
public function getToken()
{
return $this->settings['token'] ?? '';
}
}
Polymorphic Relationship
To handle this I used a polymorphic relationship in Laravel to handle the different types of settings. I created
a Setting model that would be used to handle the different types of settings.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
public function notification()
{
return $this->morphTo('type', 'type', 'id');
}
}
The notification method is a polymorphic relationship that will return the correct model based on the type column of
the settings table.
In order to tell Laravel which model to use for each type we need to add a morphMap to the AppServiceProvider class.
<?php
namespace App\Providers;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Relation::morphMap([
'email' => EmailSetting::class,
'slack' => SlackSetting::class,
]);
}
}
The morphMap method will map the email and slack types to the EmailSetting and SlackSetting classes
respectively.
Now when you retrieve a setting from the database you can access the correct model based on the type column.
$setting = Setting::find(1);
if($setting->type === 'email') {
$email = $setting->notification->getEmail();
}
if($setting->type === 'slack') {
$channel = $setting->notification->getChannel();
$token = $setting->notification->getToken();
}
But with this you will get an error when you try to use ->notification as it will try to access the
the email_settings table that doesn't exist.
To fix this you need to tell the EmailSetting model what table to use by using the method getTable.
public function getTable()
{
return 'settings';
}
This will tell the EmailSetting model to use the settings table instead of the email_settings table and return the
right model for you when you use the notification settings.
This is how you can use polymorphic models by type in Laravel to handle different types of settings in a single database table. This can be useful when you have different types of settings that you need to handle in your application.
I hope this article has helped you to understand how to use polymorphic models by type in Laravel.