loading...

Simple tag trait for Laravel models

bdelespierre profile image Benjamin Delespierre ・1 min read

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');

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));
        }
    }
}

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;
    }
}

Note: for the HasName trait, see this post


app/Post.php

<?php

namespace App;

class Post
{
    use Taggable;

    // ...
}

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, '');
}

Posted on by:

bdelespierre profile

Benjamin Delespierre

@bdelespierre

CTO at AddWorking, I write Laravel snippets all the time

Discussion

pic
Editor guide