DEV Community

Benjamin Delespierre
Benjamin Delespierre

Posted on

Simple tag trait for Laravel models

Tags are very common in any application. This snippet leverages Eloquent's polymorphism to give any model taggable capabilities.

Usage

$model->addTag('trending')->addTag('wonderful');
$model->hasTag('trending'); // true
$model->hasAnyTag('important', 'valuable', 'woderful'); // true
$model->removeTag('trending');
Enter fullscreen mode Exit fullscreen mode

Implementation

app/HasTags.php

<?php

namespace App;

use App\Tag;
use Illuminate\Support\Arr;

trait HasTags
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }

    public function hasTag($tag): bool
    {
        return $this->tags()->get()->contains(Tag::wrap($tag));
    }

    public function hasAnyTag(...$tags): bool
    {
        foreach (Arr::flatten($tags) as $tag) {
            if ($this->hasTag($tag)) {
                return true;
            }
        }

        return false;
    }

    public function addTag(...$tag): void
    {
        foreach (Arr::flatten($tag) as $tag) {
            $this->tags()->attach(Tag::wrap($tag));
        }
    }

    public function removeTag(...$tag): void
    {
        foreach (Arr::flatten($tag) as $tag) {
            $this->tags()->detach(Tag::wrap($tag));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

app/Tag.php

<?php

namespace App;

use App\HasName;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    use HasName;

    protected $fillable = [
        'name',
    ];

    public function __toString()
    {
        return (string) $this->name;
    }

    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    public function setNameAttribute($value)
    {
        if (! preg_match($regex = '/^[a-z0-9_]+$/', $value)) {
            throw new \InvalidArgumentException("invalid tag format: {$value} (it should match $regex)");
        }

        $this->attributes['name'] = $value;
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: for the HasName trait, see this post


app/Post.php

<?php

namespace App;

class Post
{
    use Taggable;

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Bonus

Here's a JavaScript function to cleanup a tag input in the front-end:

function cleanupTag(tag) {
    // see https://stackoverflow.com/a/37511463/381220
    return tag.toLowerCase()
        .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
        .replace(/[^a-z0-9_]*/g, '');
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)