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.