Enhancing Laravel Applications with Traits: A Step-by-Step Guide

Enhancing Laravel Applications with Traits: A Step-by-Step Guide

What are traits?

Traits are a mechanism for code reuse. Traits allow you to create methods that can be used in multiple classes to avoid code duplication. They are intended to enhance traditional inheritance, enabling you to reuse sets of methods freely in several independent classes living in different class hierarchies.

Creating a Trait

Traits are classes nothing more, typically you would create them in a traits folder or concerns which has become quite popular in Laravel. I use a traits folder.

When the traits for written to be used in models I place the traits folder inside a app/Models folder.

The skeleton for a trait is as follows:

<?php

namespace App\Models\Traits;

trait HasUuid
{
    public static function booted()
    {
        ...
    }

}

Like any other class, a trait needs a namespace so it can be loaded.

Next name the trait in this example the trait is called HasUuid.

Inside the traits are either public methods that can be called or like in this case a named function that would be loaded automatically by Laravel using a special booted name.

Usage

To use the above trait in a model. The trait will be imported by calling its namespace use App\Models\Traits\HasUuid;

Then inside the class use followed by the trait name.

<?php

namespace App\Models;

use App\Models\Traits\HasUuid;

class Setting extends Model
{
    use HasUuid;

    ...
}

A complete UUID trait

In the above examples I’ve mentioned using a trait called HasUuid

The complete class:

<?php

namespace App\Models\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

trait HasUuid
{
    public function getIncrementing(): bool
    {
        return false;
    }

    public function getKeyType(): string
    {
        return 'string';
    }

    public static function booted()
    {
        static::creating(function (Model $model) {
            // Set attribute for new model's primary key (ID) to an uuid.
            $model->setAttribute($model->getKeyName(), Str::uuid()->toString());
        });
    }
}

This trait contains 3 methods. In this case, they are all used internally by Laravel.

getIncrementing is used to turn off auto-incrementing IDs in a database for a primary key column.

getKeyType is used to override the getKeyType method, which is defined in the parent classes/interfaces. In Laravel, the getKeyType method is used to specify the data type of the primary key in the model associated with the trait.

In this specific example, the getKeyType method is hard-coded to always return the string 'string'. This means that any model using this trait will have its primary key treated as a string type. For instance, if you have a model that uses this trait, and you retrieve its primary key type using $model->getKeyType(). it will always return string regardless of the actual data type of the primary key column in the database.

booted will be called automatically by Laravel, since the method will be called statically it must be defined as a static method.

Creating a custom trait

In Laravel you can’t boot multiple traits so instead it's better to name a trait boot method in the format of bootHasName so a trait called HasRoles would be called bootHasRoles

This allows booting multiple traits and will still be called automatically when they are used inside other classes.

A common use case is building a multi-tenancy system. Where when a user is logged in then grab a column from the table such as tenant_id and then populate any model with the value of the current tenant.

This avoids having to define a tenend_id any time a model has a create event, this trait will automatically see the create event, and populate a field called tenant_id and then apply a global function so only records matching this tenant will be used.

<?php

namespace App\Models\Traits;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

trait HasTenant
{
    public static function bootHasTenant(): void
    {
        if (auth()->check()) {
            $tenantId = auth()->user()->tenant_id;

            static::creating(function (Model $model) use ($tenantId) {
                $model->tenant_id = $tenantId;
            });

            static::addGlobalScope('tenant', function (Builder $builder) use ($tenantId) {
                $builder->where('tenant_id', $tenantId);
            });
        }
    }
}

To use both these examples in a single model:

<?php

namespace App\Models;

use App\Models\Traits\HasTenant;
use App\Models\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;

class Setting extends Model
{
    use HasTenant;
    use HasUuid;

     //rest of the model code
}

Conclusion

Traits in Laravel offer a powerful way to reuse code across different classes, thereby reducing code duplication. By utilizing concepts such as the HasUuid and HasRoles traits, Laravel applications can be made more efficient and maintainable.

Furthermore, Laravel's unique booting mechanism allows for the automatic execution of trait methods, simplifying the implementation of complex functionalities like multi-tenancy. Therefore, understanding and effectively using traits is an essential skill for any Laravel developer.

Did you find this article valuable?

Support David Carr by becoming a sponsor. Any amount is appreciated!