DoctrineExtensions: Sluggable

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Since slug is just a normal field, we could open our fixtures file and add the slug manually here to set it:

AppBundle\Entity\Genus:
genus_{1..10}:
name: <genus()>
... lines 4 - 37

LAME! There's a cooler way: what if it were automagically generated from the name? That would be awesome! Let's go find some magic!

Installing StofDoctrineExtensionsBundle

Google for a library called StofDoctrineExtensionsBundle. You can find its docs on Symfony.com. First, copy the composer require line and paste it into your terminal:

Tip

If you are on Symfony 3.2 or higher, you don't have to specify the bundle's version

composer require stof/doctrine-extensions-bundle:1.2

Second, plug the bundle into your AppKernel: copy the new bundle statement, open app/AppKernel.php and paste it here:

... lines 1 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
... lines 11 - 21
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
... lines 23 - 24
);
... lines 26 - 35
}
... lines 37 - 56
}

And finally, the bundle needs a little bit of configuration. But, the docs are kind of a bummer: it has a lot of not-so-important stuff near the top. It's like a treasure hunt! Hunt for a golden cold block near the bottom that shows some timestampable config.yml code. Copy this. Then, find our config.yml file and paste it at the bottom. And actually, the only thing we need is under the orm.default key: add sluggable: true:

... lines 1 - 75
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
sluggable: true

This library adds several different magic behaviors to Doctrine, and sluggable - the automatic generation of a slug - is just one of them. And instead of turning on all the magic features by default, you need to activate the ones that you want. That's actually pretty nice. Another great behavior is Timestampable: an easy way to add createdAt and updatedAt fields to any entity.

The DoctrineExtensions Library

Head back to the documentation and scroll up. Near the top, find the link called DoctrineExtensions documentation and click it.

The truth is, StofDoctrineExtensionsBundle is just a small wrapper around this DoctrineExtensions library. And that means that most of the documentation also lives here. Open up the Sluggable documentation, and find the code example.

Adding the Sluggable Behavior

Ok cool, this is easy. Copy the Gedmo use statement above the entity: it's needed for the annotation we're about to add. Open Genus and paste it there:

... lines 1 - 7
use Gedmo\Mapping\Annotation as Gedmo;
... lines 9 - 168

Then, above the slug field, we'll add this @Gedmo\Slug annotation. Just change fields to simply name:

... lines 1 - 7
use Gedmo\Mapping\Annotation as Gedmo;
... lines 9 - 14
class Genus
{
... lines 17 - 29
/**
* @ORM\Column(type="string", unique=true)
* @Gedmo\Slug(fields={"name"})
*/
private $slug;
... lines 35 - 166
}

That is it! Now, when we save a Genus, the library will automatically generate a unique slug from the name. And that means we can be lazy and never worry about setting this field ourselves. Nice.

Reload the Fixtures

Head back to your terminal. Woh! My composer require blew up! But look closely: the library did install, but then it errored out when it tried to clear the cache. This is no big deal, and was just bad luck: I was right in the middle of adding the config.yml code when the cache cleared. If I run composer install, everything is happy.

Now, because our fixtures file sets the name property, we should just be able to reload our fixtures and watch the magic:

./bin/console doctrine:fixtures:load

So far so good. Let's check the database. I'll use the doctrine:query:sql command:

./bin/console doctrine:query:sql 'SELECT * FROM genus'

Got it! The name is Balaena and the slug is the lower-cased version of that. Oh, and at the bottom, one of the slugs is trichechus-1. There are two genuses with this name. Fortunately, the Sluggable behavior guarantees that the slugs stay unique by adding -1, -2, -3 etc when it needs to.

So the slug magic is all done. Now we just need to update our app to use it in the URLs.

Leave a comment!

This course is built on Symfony 3, but most of the concepts apply just fine to newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
        "stof/doctrine-extensions-bundle": "^1.2" // v1.2.2
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}