April 18, 2024 • 4 min read
Traits are a mechanism for code re-use in single inheritance languages such as PHP. They were designed to allow us developers to save logic and re-use it across different places in our applications. A class can contain as many traits as required, but functions must not collide. If more than 1 trait contain the same function name, we need to specify which one to use in the class that's using the traits.
When can we use traits? Basically when we want to encapsulate code that could run in different parts of our logic. For example,we can make a visitors count for different kind of resources (Posts and Projects) in our Laravel app. They are a good example because we could encapsulate the store visitor logic and re-use it across different models.
Let's first define the table, model and trait.
create_visitors_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('visitors', function (Blueprint $table) {
$table->id();
$table->ipAddress('ip');
$table->string('user_agent');
$table->string('url', 500);
$table->string('model_type');
$table->unsignedBigInteger('model_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('visitors');
}
};
App\Models\Visitor.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Visitor extends Model
{
use HasFactory;
protected $fillable = [
'ip',
'user_agent',
'url',
'model_type',
'model_id',
];
}
App\Traits\HasVisitorCounter.php
<?php
namespace App\Traits;
use App\Models\Visitor;
use Illuminate\Database\Eloquent\Relations\MorphMany;
trait HasVisitorCounter
{
public function visitors(): MorphMany
{
return $this->morphMany(Visitor::class, 'model');
}
public function storeVisitor()
{
return $this->visitors()->create([
'ip' => request()->ip(),
'user_agent' => request()->userAgent(),
'url' => request()->fullUrl(),
]);
}
public function visitorsCount()
{
return $this->visitors()->count();
}
}
This is the basic structure that we need to use the trait. Now let's add it to the Post and Project models and see how we can re-use the storeVisitor function
App\Models\Post.php
<?php
namespace App\Models;
use App\Traits\HasVisitorCounter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory, HasVisitorCounter;
protected $fillable = [
'title',
'subtitle',
'content',
'slug',
'post_category_id',
'image',
'is_published',
'published_at'
];
...
}
App\Models\Project.php
<?php
namespace App\Models;
use App\Traits\HasVisitorCounter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
use HasFactory, HasVisitorCounter;
protected $fillable = [
'title',
'slug',
'subtitle',
'content',
'image'
];
...
}
With that set in place, we can use the storeVisitor() in our model at any place that we want. Particularly, I'm going to use it whenever I render a Post or Project in the application.
App\Http\Controllers\Web\PostController.php
/**
* Display the specified resource.
*/
public function show(Post $post)
{
// Trait method
$post->storeVisitor();
$relatedPosts = Post::published()
->where(function($query) use ($post) {
$query->where('post_category_id', $post->post_category_id)
->orWhereHas('tags', function ($query) use ($post) {
$query->where(function($q) use ($query, $post) {
$query->where('model_has_tags.taggable_type', Post::class)
->whereIn('model_has_tags.tag_id', $post->tags->pluck('id'));
});
});
})
->where('posts.id', '!=', $post->id)
->limit(4)
->inRandomOrder()
->get();
return view('post.show', [
'breadcrumbs' => $this->breadcrumbsService->getBlogPostBreadcrumbs($post),
'post' => $post,
'relatedPosts' => $relatedPosts
]);
}
App\Http\Controllers\Web\ProjectController.php
/**
* Display the specified resource.
*/
public function show(Project $project)
{
// Trait method
$project->storeVisitor();
return view('project.show', [
'breadcrumbs' => $this->breadcrumbsService->getProjectPublicBreadcrumbs($project),
'project' => $project,
]);
}
Traits are a powerful tool that can help us maintain our codebase clean and avoid having to apply the same modification on N different places in our application. With their proper usage, it also helps us to enhance code readability and to apply the DRY principle.
Liked the post? Feel free to share :)
-Gonza
Sr. Software Engineer
I have over 6 years of experience building highly scalable web applications using a wide variety of technologies. Currently focusing on Laravel, Livewire, Vue.js and AWS as my go-to stack.
If you enjoy the content that I make, you can subscribe and receive insightful information through email. No spam is going to be sent, just updates about interesting posts or specialized content that I talk about.
Ready to take your project to the next level?
Contact me