If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
I want to show you a little bit of Doctrine magic by using an open source library called DoctrineExtensions. The first bit of magic we’ll add is a slug to Event. Not the jabba the hutt variety, but a property that is automatically cleaned and populated based on the event name.
Head over to knpbundles.com and search for doctrine extension. The StofDoctrineExtensionsBundle is what we want: it brings in that DoctrineExtensions library and adds some Symfony glue to make things really easy. Click into its documentation.
Installing a bundle is always the same 3 steps. First, use Composer’s require command and pass it the name of the library:
php composer.phar require stof/doctrine-extensions-bundle
If it asks you for a version, type ~1.1.0. In the future, Composer should decide the best version for you.
Like we’ve seen before, the require command just added the library to composer.json for us and started downloading it.
Second, add the new bundle to your AppKernel:
// app/AppKernel.php
// ...
public function registerBundles()
{
$bundles = array(
// ...
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
);
// ...
}
And third, configure the bundle by copying a few lines from the README:
# app/config/config.yml
# ...
stof_doctrine_extensions:
orm:
default: ~
All of the details on how to install a bundle and configure it will always live in its documentation.
This bundle brings in a bunch of cool features, which we have to activate manually in config.yml. The first is called “sluggable”:
# app/config/config.yml
# ...
stof_doctrine_extensions:
orm:
default:
sluggable: true
Open up the Event entity and add a new property called slug:
// src/Yoda/EventBundle/Entity/Event.php
// ...
/**
* @ORM\Column(length=255, unique=true)
*/
protected $slug;
This is just a normal property that will store a URL-safe and unique version of the event’s name. And now let’s add the getter and setter:
// src/Yoda/EventBundle/Entity/Event.php
// ...
public function getSlug()
{
return $this->slug;
}
public function setSlug($slug)
{
$this->slug = $slug;
}
Ready for the magic? Let’s see if we can get the slug field to be automatically populated for us, based on the event’s name.
The StofDoctrineExtensionBundle is actually just a wrapper around another library called DoctrineExtensions that does most of the work. We can go to its README to get real usage details. Find the sluggable section and look at the first example.
This library works via annotations, so copy and paste the new use statement into Event. Next, copy the annotation from the slug field and change the fields option to only include name:
// src/Yoda/EventBundle/Entity/Event.php
// ...
use Gedmo\Mapping\Annotation as Gedmo;
// ...
class Event
{
// ...
/**
* @Gedmo\Slug(fields={"name"}, updatable=false)
* @ORM\Column(length=255, unique=true)
*/
protected $slug;
}
This says that we want DoctrineExtensions to automatically set the slug field based on the name property. If we also set updatable to false, it tells the library to set slug once and never change it again, even if the event’s name changes. That’s good because the slug will be used in the event’s URL. And changing URLs is lame :).
Let’s try it! Update the database schema:
php app/console doctrine:schema:update --force
This explodes because our existing events will all temporarily have blank slugs, which isn’t unique. Drop the schema and rebuild from scratch to get around this:
php app/console doctrine:schema:drop --force
php app/console doctrine:schema:create
php app/console doctrine:fixtures:load
Reload the fixtures and check the results by querying for events via the console:
php app/console doctrine:query:sql "SELECT * FROM yoda_event"
Hey, we have slugs! That’s not something you would be excited about outside of programming. As an added bonus, if two events have the same name, the library will automatically add a -1 to the end of the second slug. The library has our back and makes sure that these are always unique.
Yo Dimitry K!
Good pro tip indeed! Life is easy with migrations if you haven't deployed yet... not so easy once you *have* deployed.
For data migrations like this that can't be easily accomplished by writing some SQL, we here at KnpU typically write custom, single-use console commands. That's a nice way to get access to Symfony's normal tools (like the EntityManager). We also always make sure to write them in a way where we can restart them if something explodes (because often with data migrations, you are handling a LOT of data, and it's always possible something will go wrong). For example, in this case, we would only try to set the slug on records that don't already have a slug (so that if we restart the command, it doesn't start way back at the beginning).
Glad you added the comment - this data migration stuff is something we handle all the time in the real world!
Cheers!
Hey thanks for the great content!
I just installed added the Blameable to my entities and I am finding that it doesnt work when I try to load data using fixtures, any idea why this is?
Hey Shaun,
Did you follow this instruction: https://github.com/Atlantic... ? Also, StofDoctrineExtensionsBundle do not enable any behavior by default, you need to enable it manually in its bundle configuration in order to use it. And yeah, don't forget to clear the cache just in case ;)
Cheers!
Thanks for your reply Victor, yes I have enabled the behaviour in config/packages/stof_doctrine_extensions.yaml
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
blameable: true
timestampable: true
I also cleared the cache however I still get the same error:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'created_by' cannot be null
I am wondering if there is an active 'user' for blameable to set when a data fixture file is being run?
Hey Shaun,
I think I know the problem: this behavior sets up created_by to the *current* user, but since you're in console - there's no current user. For your fixtures, you need to manually set up the proper user. Maybe there's a smarter solution, but I don't know if it exists :)
Cheers!
Hi, Ryan!
What about KnpLabs/DoctrineBehaviors bundle? It seems also a very popular bundle that looks like StofDoctrineExtensionsBundle with similar features. Do you use it? What bundle do you like more to use in your projects?
Thanks!
Hey!
Actually, I don't use this - and I somehow haven't even really seen it yet (unfortunately, there is an ocean between me and the main Knp office, so I can miss things)! But I really like it - in many cases, the functionality that's similar to StofDoctrineExtensionsBundle looks easier to implement - for example Sluggable. I'd like to try this and tell more people about it.
Thanks!
Yes, it's a bit simple for me too. And it has less magic than in StofDoctrineExtensionsBundle :)
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "~2.4", // v2.4.2
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.0.1
"symfony/assetic-bundle": "~2.3", // v2.3.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.5
"symfony/monolog-bundle": "~2.4", // v2.5.0
"sensio/distribution-bundle": "~2.3", // v2.3.4
"sensio/framework-extra-bundle": "~3.0", // v3.0.0
"sensio/generator-bundle": "~2.3", // v2.3.4
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"doctrine/doctrine-fixtures-bundle": "~2.2.0", // v2.2.0
"ircmaxell/password-compat": "~1.0.3", // 1.0.3
"phpunit/phpunit": "~4.1", // 4.1.0
"stof/doctrine-extensions-bundle": "~1.1.0" // v1.1.0
}
}
Ryan thank you again for amazing video. Just wanted to add a "pro-tip". In the video you take shortcut and recreate all the schema when adding `slug`. In real-life scenarios most of the time slug will have to be added to already existing database and thus dropping and recreating schema wouldn't be an option. There's few good examples and proper explanation of how to add slug to existing database in this thread http://stackoverflow.com/qu...