Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

An opinionated tagging package for Laravel apps

There are a lot of quality tagging packages out there. Most of them offer the same thing: creating tags, associating them with models and some functions to easily retrieve models with certain tags. But in our projects at Spatie we need more functionality. Last week we released our own – very opinionated – tagging package aptly called laravel-tags. It comes with batteries included. Out of the box it has support for translating tags, multiple tag types and sorting capabilities.

After the package is installed (be sure to check the requirements first) the only thing you have to do is to add the HasTags trait to an Eloquent model to make it taggable.

use Illuminate\Database\Eloquent\Model;
use Spatie\Tags\HasTags;

class NewsItem extends Model
{
    use HasTags;
    ...
}

Here are some code examples of what that trait allows you to do:

//create a model with some tags
$newsItem = NewsItem::create([
   'name' => 'testModel',
   'tags' => ['tag', 'tag2'], //tags will be created if they don't exist
]);

//attaching tags
$newsItem->attachTag('tag3');
$newsItem->attachTags(['tag4', 'tag5']);

//detaching tags
$newsItem->detachTag('tag3');
$newsItem->detachTags(['tag4', 'tag5']);

//syncing tags
$newsItem->syncTags(['tag1', 'tag2']); // all other tags on this model will be detached

//retrieving models that have any of the given tags
NewsItem::withAnyTags(['tag1', 'tag2']);

//retrieve models that have all of the given tags
NewsItem::withAllTags(['tag1', 'tag2']);

Under the hood those tags will be converted to Spatie\Tags\Tag models.

Adding translations

If you’re creating a multilingual app it’s really easy to translate the tags. Here’s a quick example.

$tag = Tag::findOrCreate('my tag'); //store in the current locale of your app

//let's add some translation for other languages
$tag->setTranslation('name', 'fr', 'mon tag');
$tag->setTranslation('name', 'nl', 'mijn tag');

//don't forget to save the model
$tag->save();

$tag->getTranslation('name', 'fr'); // returns 'mon tag'

$tag->name // returns the name of the tag in current locale of your app.

The translations of the tags are stored in the name column of the tags table. It’s a json column. To find a tag with a specific translation you can just use Laravel’s query builder which has support for json columns.

 \Spatie\Tags\Tag
   ->where('name->fr', 'mon tag')
   ->first();

Behind the scenes spatie/laravel-translatable is used. You can use any method provided by that package.

Using tag types

In your application you might want to have multiple collections of tags. For example: you might want one group of tags for your News model and another group of tags for your BlogPost model.

To create separate collections of tags you can use tag types.

//creating a tag with a certain type
$tagWithType = Tag::create('headline', 'newsTag'):

In addition to strings, all methods mentioned previously in this post can take instances of Tag as well.

$newsItem->attachTag($tagWithType);
$newsItem->detachTag($tagWithType);
...

The provided method scopes, withAnyTags and withAllTags, can take instances of Spatie\Tags\Tag too:

$tag = Tag::create('gossip', 'newsTag');
$tag2 = Tag::create('headline', 'newsTag');

NewsItem::withAnyTags([$tag, $tag2])->get();

To get all tags with a specific type use the getWithType method.

$tagA = Tag::findOrCreate('tagA', 'firstType');
$tagB = Tag::findOrCreate('tagB', 'firstType');
$tagC = Tag::findOrCreate('tagC', 'secondType');
$tagD = Tag::findOrCreate('tagD', 'secondType');

Tag::getWithType('firstType'); // returns a collection with $tagA and $tagB

//there's also a scoped version
Tag::withType('firstType')->get(); // returns the same result

Sorting tags

Whenever a tag is created it’s order_column will be set the highest value in that column + 1.

$tag = Tag::findOrCreate('tagA');
$tag->order_column; // returns 1
$tag = Tag::findOrCreate('tagB');
$tag->order_column; // returns 2

Under the hood spatie/eloquent-sortable is used, so you can use any model provided by that package. Here are some examples:

//get all tags sorted on `order_column`
$orderedTags = Tags::ordered()->get(); 

//set a new order entirely
Tags::setNewOrder($arrayWithTagIds);

$myModel->moveOrderUp();
$myModel->moveOrderDown();

//move the tag to the first or last position
$myModel->moveToStart();
$myModel->moveToEnd();

$tag->swapOrder($anotherTag);

Of course you can also manually change the value of the order_column.

$tag->order_column = 10;
$tag->save();

If you want to know more about the package check out the documentation. Want to read at the source code (or ⭐ the project 😙)? Head over the repo to GitHub. This isn’t the first Laravel package we’ve made, take a look at the list of Laravel packages on our company website to see if there’s anything there that can be used on your projects.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band.
  • Željko Sredojević

    Seems really good, but is laravel 5.3 requirement really necessary?

    • Thanks!

      In Laravel 5.3 some of the collection methods have changed signature. For us it’s easier to maintain code without needing to keep those changes in mind. Every project that we start at our company is a Laravel 5.3 one, so for us there’s not point in trying to create packages that are compatible with older versions of the framework.

      You’re of course free to fork your own copy that is compatible with older Laravel apps.

      • Željko Sredojević

        Thank you for the hint, I really appreciate all your effort!

  • Jeroen Noten

    Thanks! This is so powerful!

    BTW, a small typo in one of the code examples (missing closing bracket):


    //syncing tags
    $newsItem->syncTags(['tag1', 'tag2');

  • Bruno Seixas

    Looks really helpful and powerful.

  • you forgot to mention that laravel-tags also uses latest MySQL’s json-based WHEREs, so minimal requirement is MySQL 5.7. this probably won’t work neither with earlier MySQL versions nor with MariaDB.

    • I did put a link to the requirements in the docs. The db requirement is mentioned there.
      https://docs.spatie.be/laravel-tags/v1/requirements

      • Skattabrain

        Hi Freek, this looks awesome. Was disappointed I can’t use it as I’m on Mariadb. Any plans on making in compatible with Mariadb?

        • No, not really, you’ll need MySQL 5.7 or higher in order to use this package. You can fork the code and maintain your own Mariadb-compatible copy.