Back to Blog
Lesson 30 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20263 min read

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.

LaravelPHPRefactoringOOPEloquentbackend

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:

PHP
namespace 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.

PHP
namespace 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.

PHP
class 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

  1. Create a HasUuid trait that automatically generates a UUID when creating a new model instance.
  2. Apply this trait to your Project model.
  3. Ensure your Project migration has a uuid column.
  4. Test that creating a Project via Project::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() or delete()).

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.

Similar Posts