Using Traits for Code Reuse: A Practical Laravel Guide
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.
The Problem: When Inheritance Isn't Enough
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.
Creating a Reusable Trait
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 Traits to Models
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.
Resolving Trait Conflicts
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."
Hands-on Exercise
- Create a
HasUuidtrait that automatically generates a UUID when creating a new model instance. - Apply this trait to your
Projectmodel. - Ensure your
Projectmigration has auuidcolumn. - Test that creating a
ProjectviaProject::create(['name' => 'Test'])correctly populates the UUID field.
Common Pitfalls
- Over-using Traits: Traits are not a substitute for proper service-oriented architecture. If your trait contains complex business logic, it belongs in a Service class, not a trait. Keep traits limited to cross-cutting concerns like database helpers, formatting, or simple state management.
- Hidden Dependencies: A trait might rely on a specific column existing in the database (e.g.,
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. - Namespace Collisions: Always use descriptive names for your trait methods to avoid clashing with standard Eloquent methods (e.g., don't name a trait method
save()ordelete()).
Recap
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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.