PHP Traits are a powerful feature that allows you to reuse code in multiple classes without repeating yourself.
In this tutorial, I will show 4 examples of how you can use Traits in your Eloquent models.
- Common Relationships
- Reusable Methods
- Extending Traits
- Adding Listeners from Traits
Let's dive in!
Example 1: Common Relationships
Wanna have the same set of relationships on multiple models? Traits can help.
An example can be found in spatie laravel-permissions package, it has a trait called HasRoles:
/** * A model may have multiple roles. */public function roles(): BelongsToMany{ $relation = $this->morphToMany( config('permission.models.role'), 'model', config('permission.table_names.model_has_roles'), config('permission.column_names.model_morph_key'), app(PermissionRegistrar::class)->pivotRole ); if (! app(PermissionRegistrar::class)->teams) { return $relation; } return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()) ->where(function ($q) { $teamField = config('permission.table_names.roles').'.'.app(PermissionRegistrar::class)->teamsKey; $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()); });}
This Trait has a roles() relationship. This means that the following Model, using this Trait, will have the roles() relationship, too:
app/Models/User.php
use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable{ use HasRoles; // ...}
And if you need this relationship on another model, you can just add the use HasRoles trait to that model as well. Sounds great, right?
Example 2: Reusable Methods
Another great use case for Traits is to have reusable methods.
Let's take a look at the HasTags trait from spatie laravel-tags package:
public function scopeWithoutTags( Builder $query, string | array | ArrayAccess | Tag $tags, string $type = null): Builder { $tags = static::convertToTags($tags, $type); return $query ->whereDoesntHave('tags', function (Builder $query) use ($tags) { $tagIds = collect($tags)->pluck('id'); $query->whereIn('tags.id', $tagIds); });}
Reusable scopes will be available on all of your models that use the HasTags trait.
Also, that Trait has methods that attach/detach single/multiple tags:
public function attachTags(array | ArrayAccess | Tag $tags, string $type = null): static{ $className = static::getTagClassName(); $tags = collect($className::findOrCreate($tags, $type)); $this->tags()->syncWithoutDetaching($tags->pluck('id')->toArray()); return $this;} public function attachTag(string | Tag $tag, string | null $type = null){ return $this->attachTags([$tag], $type);} public function detachTags(array | ArrayAccess $tags, string | null $type = null): static{ $tags = static::convertToTags($tags, $type); collect($tags) ->filter() ->each(fn (Tag $tag) => $this->tags()->detach($tag)); return $this;} public function detachTag(string | Tag $tag, string | null $type = null): static{ return $this->detachTags([$tag], $type);}
All of those will become available on our Model as soon as we use the Trait:
app/Models/Post.php
use Spatie\Tags\HasTags; class Post extends Model{ use HasTags; // ...}
And now we can use those methods in our model:
$post = Post::create(['title' => 'My first post']);$post->attachTag('laravel');
How cool is that? We just stopped repeating ourselves and made our code more readable and maintainable.
As you can see, package creators often use Traits to add a common repeating functionality into our applications.
Example 3: Extending Traits
In some cases, you want to change how the trait works.
Let's take a look at the InteractsWithMedia trait from the spatie laravel-medialibrary package.
Let's take a look at the getFallbackMediaUrl() method:
public function getFallbackMediaUrl(string $collectionName = 'default', string $conversionName = ''): string{ $fallbackUrls = optional($this->getMediaCollection($collectionName))->fallbackUrls; if (in_array($conversionName, ['', 'default'], true)) { return $fallbackUrls['default'] ?? ''; } return $fallbackUrls[$conversionName] ?? $fallbackUrls['default'] ?? '';}
It seems to be close to what we need but returns an empty string when no media is found. Instead, we want it to return a default image.
To solve that, we can add the method to our Post model, copy-paste its code from the pagkage and override the default behavior:
/app/Models/Post.php
use Spatie\MediaLibrary\HasMedia;use Spatie\MediaLibrary\InteractsWithMedia; class Post extends Model implements HasMedia{ use InteractsWithMedia; // ... public function getFallbackMediaUrl(string $collectionName = 'default', string $conversionName = ''): string { $fallbackUrls = optional($this->getMediaCollection($collectionName))->fallbackUrls; $defaultMediaUrl = "https://laravel.com/img/logotype.min.svg"; if (in_array($conversionName, ['', 'default'], true)) { return $fallbackUrls['default'] ?? $defaultMediaUrl; } return $fallbackUrls[$conversionName] ?? $fallbackUrls['default'] ?? $defaultMediaUrl; }}
Example 4: Adding Listeners from Traits
Another use case for traits might be quickly adding common tracking methods.
For example, if you want a specific action to happen when you are deleting a model. This use case can be found in the previously mentioned spatie laravel-medialibrary
Let's find the bootInteractsWithMedia() method and see what it does:
public static function bootInteractsWithMedia(){ static::deleting(function (HasMedia $model) { if ($model->shouldDeletePreservingMedia()) { return; } if (in_array(SoftDeletes::class, class_uses_recursive($model))) { if (! $model->forceDeleting) { return; } } $model->media()->cursor()->each(fn (Media $media) => $media->delete()); });}
It's looking for the deleting event and then deleting all media attached to the model, unless flags are set to preserve media.
This is a quick way to add a Listener to a Model without having to add it to the Model itself.
These boot methods have naming rules to be autoloaded by Laravel:
- Starts with
boot - Includes trait name in
PascalCase
For example:
boot + InteractsWithMedia = bootInteractsWithMedia
Conclusion
Traits are a great way to reuse code and make your code more readable and maintainable. You can find more examples of Traits in our section of code examples.