Laravel Polymorphic Models By Type
Tags: webdev, laravel, tutorial, php
Published: 2024-05-30 08:51:20

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.

```php
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
<?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
<?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
<?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
<?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.

```php
$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.

```php
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.