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, '');
}
Top comments (0)