Black Friday: coupon FRIDAY24 for 40% off Yearly/Lifetime membership! Read more here
Courses

PHP for Laravel Developers

Interface with Trait: Spatie Media Model

To complete the whole picture, the last example in this sequence comes from a well-known package Spatie Media Library.

Here's the primary information from their installation instructions:

To associate media with a model, the Model must implement the following interface and Trait:

namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
 
class YourModel extends Model implements HasMedia
{
use InteractsWithMedia;
}

So, here we have all three things we discussed earlier:

  • extends XYZ
  • implements XYZ
  • use XYZ

And I often see this structure in Laravel packages:

  1. They provide the interface with the rules (HasMedia)
  2. And they also provide their own implementation of that interface (InteractsWithMedia)

laravel-medialibrary/src/HasMedia.php:

interface HasMedia
{
public function media(): MorphMany;
 
public function addMedia(string|UploadedFile $file): FileAdder;
 
// ... more methods
 
public function registerMediaConversions(Media $media = null): void;

In addition to that interface, the package provides the Trait to implement all those methods.

laravel-medialibrary/src/InteractsWithMedia.php:

trait InteractsWithMedia
{
/** @var Conversion[] */
public array $mediaConversions = [];
 
// ...
 
public function media(): MorphMany
{
return $this->morphMany(config('media-library.media_model'), 'model');
}
 
// ...

In other words, the package gives you the rules and its own variant of how they follow those rules.

But in your Eloquent Model, you're free to override the implementation.

The difference from the Filament canAccessPanel() method is that Filament didn't "know" how to implement it. It's all on you as a user of the package. In Spatie's case, they "know" how that media() should be a polymorphic relation to their Media model but also make it configurable for you.


A Few More Examples of Interface + Trait

I've found a few more packages that work with the same logic.

Laratrust package has this in the installation instructions:

use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Illuminate\Foundation\Auth\User as Authenticatable;
 
class User extends Authenticatable implements LaratrustUser
{
use HasRolesAndPermissions;
 
// ...
}

As you can see, there's an interface LaratrustUser and a trait HasRolesAndPermissions.

Another example comes from the same Spatie team but in a different package Eloquent Sortable. Here's what you need to define in your Eloquent Model:

use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
 
class MyModel extends Model implements Sortable
{
use SortableTrait;
 
public $sortable = [
'order_column_name' => 'order_column',
'sort_when_creating' => true,
];
 
// ...
}

Again: implements Sortable (interface) and use SortableTrait (trait).

Such a complex OOP approach with interfaces is more widely used in packages and frameworks because they have to create some strict structure for users to follow. But it's beneficial to understand because you may never know when you work on a more complex project that would benefit from this, too.

avatar

this series is amazing, I'm one of the one who learn about laravel before php. Thank you so much Laravel Daily Teams, surely I never know about these OOP concepts before, even after creating 4 full stacks website using laravel.

👍 9