Master traits in Laravel to eliminate code duplication. Learn to create, apply, and resolve conflicts for cleaner, more maintainable Eloquent models.
Previously in this course, we explored implementing the service layer in Laravel for maintainable code to keep our controllers thin. While service classes handle business processes, we often find ourselves repeating the same utility methods across multiple Eloquent models. Today, we bridge that gap by using traits for horizontal code reuse.
In object-oriented programming, we use inheritance to share behavior. However, PHP only supports single inheritance. If you have a Task model and a Project model, and both need a sluggable URL feature or a hasUuid identifier, you cannot make them both inherit from a single BaseModel without creating a fragile, deep class hierarchy.
Traits are a mechanism for code reuse in single-inheritance languages. They allow you to inject methods into classes without forcing a specific inheritance structure. In Laravel, they are the bread and butter of framework internals—think Notifiable or HasApiTokens.
Let's evolve our project board. Suppose we want to track when a record was "archived." We want both Project and Task to have an archive() method and an isArchived scope.
Create a directory app/Traits and define your first trait:
PHPnamespace App\Traits; use Illuminate\Database\Eloquent\Builder; trait Archivable { public function archive(): bool { return $this->update(['archived_at' => now()]); } public function scopeOnlyArchived(Builder $query): Builder { return $query->whereNotNull('archived_at'); } }
Applying the trait is straightforward. You use the use keyword inside the class body. This imports the methods as if they were defined directly in the model.
PHPnamespace App\Models; use App\Traits\Archivable; use Illuminate\Database\Eloquent\Model; class Task extends Model { use Archivable; protected $fillable = ['title', 'archived_at']; }
Now, you can call $task->archive() or Task::onlyArchived()->get() seamlessly. If you find yourself doing this across many models, you are effectively refactoring your codebase toward a more modular design.
What happens if you use two traits that define the same method name? PHP will throw a fatal error. You must explicitly resolve the conflict using the insteadof operator.
Suppose you have Archivable and a hypothetical Loggable trait that both define a boot() method or a custom status() method.
PHPclass Task extends Model { use Archivable, Loggable { Archivable::status insteadof Loggable; Loggable::status as logStatus; } }
Here, we tell PHP: "Use the status method from Archivable instead of Loggable, but keep the Loggable version available under the alias logStatus."
HasUuid trait that automatically generates a UUID when creating a new model instance.Project model.Project migration has a uuid column.Project via Project::create(['name' => 'Test']) correctly populates the UUID field.archived_at). If you apply the trait to a model without that column, your code will crash at runtime. Always document these requirements in the trait's DocBlock.save() or delete()).Traits are a powerful tool for refactoring redundant code out of your models. By encapsulating shared logic into traits, you keep your models focused and your codebase DRY. Remember to use them for utility and cross-cutting concerns, and always be mindful of potential method name collisions when importing multiple traits.
Up next: We will dive into Advanced Dependency Injection with Service Providers to learn how to manage complex object creation and binding interfaces to implementations in the service container.
Master advanced Eloquent scopes to encapsulate complex business logic, chain query filters, and maintain clean, expressive models in your Laravel SaaS platform.
Read moreMaster advanced filtering, sorting, and pagination in Laravel to build scalable APIs. Learn to parse query params and implement dynamic Eloquent queries today.
Using Traits for Code Reuse