Traits en PHP y Laravel: guía práctica con ejemplos
Cómo encapsular lógica reutilizable con traits en Laravel, con el ejemplo real de un contador de visitas compartido entre los modelos Post y Project.
Introducción
Los traits son un mecanismo de reutilización de código para lenguajes de herencia simple como PHP. Están diseñados para que los desarrolladores guardemos lógica y la reusemos en distintos lugares de la aplicación. Una clase puede contener todos los traits que haga falta, pero las funciones no pueden colisionar. Si más de 1 trait tiene el mismo nombre de función, hay que especificar cuál usar en la clase que los usa.
¿Cuándo conviene usar traits? Básicamente cuando querés encapsular código que puede correr en distintas partes de tu lógica. Por ejemplo, podemos armar un contador de visitas para distintos tipos de recursos (Posts y Projects) en nuestra app Laravel. Son un buen ejemplo porque podemos encapsular la lógica de guardar la visita y reusarla en varios modelos.
Para empezar
Primero vamos a definir la tabla, el modelo y el 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();
}
}
Esta es la estructura básica que necesitamos para usar el trait. Ahora vamos a sumarlo a los modelos Post y Project para ver cómo reusar la función storeVisitor
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'
];
...
}
Con eso listo, ya podemos usar storeVisitor() en el modelo donde queramos. En particular, yo lo voy a usar cada vez que renderizo un Post o un Project en la aplicación.
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,
]);
}
Conclusión
Los traits son una herramienta potente para mantener el codebase limpio y evitar aplicar la misma modificación en N lugares distintos de la aplicación. Bien usados, también mejoran la legibilidad del código y te ayudan a aplicar el principio DRY.
¿Te sirvió el post? Si querés, compartilo :)
-Gonza
¿Necesitás ayuda con una base de código Laravel o un desarrollo como este? Mirá mi servicio de consultoría Laravel.