Master Laravel traits to simplify your Eloquent models. Learn how to implement effective PHP code reuse and follow DRY programming best practices today.

Last month, I found myself refactoring a legacy codebase where three different Eloquent models—Post, Comment, and Video—each had a 50-line scopePublished method and identical logic for handling "soft" status updates. Copy-pasting that logic was a mistake that led to a bug when one model's status transition changed but the others didn't. That’s when I turned to traits to centralize the behavior.
When you’re building a growing application, your Eloquent models often become "god objects." They start holding business logic, scopes, accessors, and event listeners. Using laravel traits allows you to extract this functionality into small, focused files that you can mix and match across different models.
If you’ve already been mastering laravel policies to keep your controllers lean, you’re already halfway to the right mindset. Traits are the next step in organizing your domain logic.
Let’s say you have a common requirement: tracking whether an item is "archived." Instead of writing the same scopeArchived query or archive() method in every model, create a trait.
PHPnamespace App\Traits; trait HasArchiveStatus { public function scopeArchived($query) { return $query->where('is_archived', true); } public function archive(): void { $this->update(['is_archived' => true]); } }
Now, applying this to your model is trivial.
PHPnamespace App\Models; use App\Traits\HasArchiveStatus; use Illuminate\Database\Eloquent\Model; class Post extends Model { use HasArchiveStatus; }
The primary goal here is dry programming (Don't Repeat Yourself). When you see the same logic in two models, it’s a candidate for a trait. If you see it in three, it’s a requirement.
However, don't over-engineer. I once spent about two days building a complex trait system for features that only ever applied to one model. That was a waste of time. Start simple. If you find yourself needing to transform data before saving, you might prefer using laravel eloquent accessors and mutators instead of a trait. Use traits for behavior (scopes, methods), and use accessors for data representation.
One common issue with php code reuse via traits is method collisions. If two traits define the same method, PHP will throw a fatal error.
You can resolve this using the insteadof operator.
PHPuse TraitA, TraitB { TraitA::boot insteadof TraitB; }
Honestly, I try to avoid this by keeping traits highly specialized. If you find yourself needing insteadof, your traits are likely doing too much. Break them down into smaller pieces.
To keep your codebase maintainable, follow these laravel best practices:
boot{TraitName} convention: If your trait needs to hook into model events (like creating or deleted), Laravel automatically calls a method named boot followed by the trait name.app/Traits to keep your file structure predictable.Don't use traits just because you can. Sometimes, a dedicated Service class is a better fit. If your logic involves multiple models or complex orchestration, look into using laravel service providers to manage those dependencies. Traits are for model-specific behavior, not for global application orchestration.
Q: Can a trait have properties? A: Yes, but be careful. Traits can have properties, but they are shared across all instances. It’s usually safer to rely on the model's attributes or relationships.
Q: How do I test a trait? A: You can’t test a trait in isolation easily. Create a dummy model in your test suite that uses the trait, and write your tests against that model.
Q: Is there a limit to how many traits I can use on a model? A: Technically no, but if you have 10+ traits on one model, your model is likely becoming impossible to debug. It's a sign that you need to re-evaluate your architecture.
I’m still refining how I handle traits in large-scale apps. Sometimes I find that I’ve abstracted too early, and I end up deleting a trait I spent hours writing. That’s okay. The goal isn't to have a perfect architecture from day one; it's to have a codebase that's easy to change when the requirements shift. Don't be afraid to pull logic back into the model if the abstraction isn't providing clear value.
Laravel dependency injection in controller methods simplifies your code. Learn how to use method injection effectively to build cleaner, more testable apps.
Read more