Leveraging Traits in Laravel Eloquent Models
Building Reusable Functionality with the Numberable Package
In Laravel development, traits are incredibly powerful tools that allow you to add reusable functionality to Eloquent models. Whether it's automating attribute generation or customizing model behavior, traits help streamline complex functionality across models while keeping your codebase clean and maintainable.
In this article, we’ll explore how traits work in Laravel, how they can be used to add shared functionality to Eloquent models, and dive into an example with a custom Numberable
package—a trait-based package for generating unique, consistent document numbers for models. We’ll also touch on how to separate a trait into a standalone Laravel package, making it reusable across projects.
What Are Traits and Why Use Them?
Traits in PHP are a mechanism for code reuse, allowing classes to inherit methods from multiple sources. In Laravel, traits are often used to add reusable methods or functionalities to Eloquent models. Instead of creating a complex inheritance hierarchy, you can simply import a trait, making your model gain additional behaviors or features without impacting the rest of the application.
Example Uses of Traits:
Automatic Timestamps: Custom traits can automatically set timestamps for specific actions.
Soft Deletes: Laravel’s
SoftDeletes
trait allows models to be marked as deleted without removing them from the database.Custom Attributes: Traits can add calculated or formatted attributes for models.
Traits are an excellent choice for sharing functionality across different models, especially when you need consistency and reusability. They allow us to keep our models lean and focused on their primary business logic.
Building the Numberable Trait: An Example
Our Numberable
package demonstrates how traits can add reusable functionality to Eloquent models. This package provides a trait, ModelHasNumber
, which automatically assigns a unique document number to each model when it’s created.
The generated number format can be customized, making this package ideal for any application that needs consistent, flexible document numbering. Here’s a brief overview of how the ModelHasNumber
trait works.
// Labrodev\Numberable\ModelHasNumber.php
namespace Labrodev\Numberable;
use Illuminate\Support\Str;
use Carbon\Carbon;
trait ModelHasNumber
{
public static function bootModelHasNumber(): void
{
static::created(function ($model) {
if (!$model->isModelHasNumberTraitValueGenerated()) {
if (isset($model->id)) {
$model->{$model->modelHasNumberTraitColumn()} = $model->generateNumberByTraitModelHasNumber($model->id);
$model->withoutEvents(function () use ($model) {
$model->save();
});
}
}
});
}
protected function modelHasNumberTraitColumn(): string
{
return 'number';
}
private function isModelHasNumberTraitValueGenerated(): bool
{
$name = $this->modelHasNumberTraitColumn();
return isset($this->attributes[$name]) && $this->attributes[$name] !== null;
}
protected function generateNumberByTraitModelHasNumber(int $modelId): string
{
$currentYear = Carbon::now()->year;
$predictionNumber = $this->predictionNumberForTraitModelHasNumber();
$paddingLength = strlen((string) $predictionNumber);
return $currentYear . Str::padLeft((string) $modelId, $paddingLength, '0');
}
protected function predictionNumberForTraitModelHasNumber(): int
{
return 10000;
}
}
How ModelHasNumber
Works
Automatic Number Generation: The trait attaches a listener to the
created
event of the model. Upon creation, it checks if the number attribute is already set, and if not, it generates a new document number.Year-Based Numbering: The number generated includes the current year as a prefix, followed by a padded model ID. This format is useful for document numbers, invoice numbers, or order IDs.
Customizable Logic: You can override the
generateNumberByTraitModelHasNumber
method within a model to provide a custom document number format. This allows you to adapt the numbering system for specific requirements.
Example Number Format: If the current year is 2024, the model ID is 45
, and the prediction number is 10000
, the document number generated would be 202400045
. This ensures a consistent document number length.
Customizing the Trait in Models
Laravel makes it easy to override trait methods within a model. If a model requires a custom numbering format, you can simply override the generateNumberByTraitModelHasNumber
method to create a unique numbering scheme without modifying the base trait.
use Labrodev\Numberable\ModelHasNumber;
class Order extends Model
{
use ModelHasNumber;
protected function generateNumberByTraitModelHasNumber(int $modelId): string
{
// Custom numbering logic for Order model
$prefix = 'ORD';
return $prefix . Carbon::now()->year . str_pad((string) $modelId, 6, '0', STR_PAD_LEFT);
}
}
In this example, the Order
model will have a custom numbering format, such as ORD2024000045
, combining a prefix with the year and a padded ID.
Packaging the Trait as a Laravel Package
Creating a standalone package for the ModelHasNumber
trait allows it to be reused across multiple projects. Here’s how to set up this package in a few steps:
Create the Package Structure: Inside your
packages
directory, create a folder for your package, for example,Labrodev/Numberable
, with asrc
folder containing theModelHasNumber.php
trait.Set Up
composer.json
: In the root of the package folder, create acomposer.json
file to define the package’s dependencies and metadata:
{
"name": "labrodev/numberable",
"description": "A Laravel package for automatic document numbering",
"type": "library",
"require": {
"php": ">=8.1",
"illuminate/contracts": "^10.0",
"nesbot/carbon": "^2.72",
"ramsey/uuid": "^4.7"
},
"require-dev": {},
"autoload": {
"psr-4": {
"Labrodev\\Numberable\\": "src/"
}
},
"scripts": {
"post-autoload-dump": "@composer run prepare",
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"build": [
"@composer run prepare",
"@php vendor/bin/testbench workbench:build --ansi"
],
"start": [
"Composer\\Config::disableProcessTimeout",
"@composer run build",
"@php vendor/bin/testbench serve"
]
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}
Conclusion
Traits are a fantastic way to add modular functionality to Eloquent models, making it easy to reuse logic across different models. The Numberable
package is a practical example of how traits can encapsulate specific functionality, in this case, document number generation. By converting it into a standalone package, we’ve made it possible to reuse this functionality across Laravel projects with ease.
Traits are especially useful when combined with Laravel’s event-driven model lifecycle, allowing us to automate processes like assigning unique numbers to models. With a little customization, traits like ModelHasNumber
can serve a variety of business needs, keeping your Laravel codebase clean, consistent, and highly maintainable.
For the full implementation, check out our laravel-numberable package on GitHub.
Thanks for reading!
Subscribe to our Substack to get more tips and tutorials on Laravel development and best practices delivered straight to your inbox.
Preview photo credit: