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.

Publicado18 de abril de 2024
Lectura4 min de lectura
CategoríaDesarrollo web
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

#Laravel #PHP 2080 visitas

¿Necesitás ayuda con una base de código Laravel o un desarrollo como este? Mirá mi servicio de consultoría Laravel.

Suscribite a mi newsletter

Si te gusta el contenido que hago, podés suscribirte y recibir información valiosa por correo. No se envía spam, solo novedades sobre publicaciones interesantes o contenido especializado del que hablo.

Gonzalo Gomez · Especialista en IA y Automatización

Posts relacionados