Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.4.1
Subscribe to download the code!Compatible PHP versions: ^7.4.1
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Sluggable: Doctrine Extensions
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe whole point of the slug
is to be a URL-safe version of the name
. And, ideally, this wouldn't be something we need to set manually... or even think about! In a perfect world, we would be able to set the name of a Question
, save and something else would automatically calculate a unique slug
from the name
right before the INSERT query.
We accomplished this in our fixtures, but only there. Let's accomplish this everywhere.
Hello StofDoctrineExtensionsBundle
To do that, we're going to install another bundle. Google for StofDoctrineExtensionsBundle and find its GitHub page. And then click over to its documentation, which lives on Symfony.com. This bundle gives you a bunch of superpowers for entities, including one called Sluggable. And actually, the bundle is just a tiny layer around another library called doctrine extensions.
This is where the majority of the documentation lives. Anyways, let's get the bundle installed. Find your terminal and run:
composer require "stof/doctrine-extensions-bundle:^1.5.0"
You can find this command in the bundle's documentation.
Contrib Recipes
And, oh, interesting! The install stops and says:
The recipe for this package comes from the contrib repository, which is open to community contributions. Review the recipe at this URL. Do you want to execute this recipe?
When you install a package, Symfony Flex looks for a recipe for that package... and recipes can live in one of two different repositories. The first is symfony/recipes, which is the main recipe repository and is closely guarded: it's hard to get recipes accepted here.
The other repository is called symfony/recipes-contrib. This is still guarded for quality... but it's much easier to get recipe accepted here. For that reason, the first time you install a recipe from recipes-contrib
, Flex asks you to make sure that you want to do that. So you can say yes or I'm actually going to say P for yes, permanently.
I committed my changes before recording, so when this finishes I'll run,
git status
to see what the recipe did! Ok: it enabled the bundle - of course - and it also created a new config file stof_doctrine_extensions.yaml
. Let's go check that out: config/packages/stof_doctrine_extensions.yaml
.
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html | |
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/ | |
stof_doctrine_extensions: | |
default_locale: en_US |
Ok... nothing too interesting yet.
Activating Sluggable in Config
As we saw, this bundle comes with a bunch of special features for entities. And each time you want to use a feature, you need to enable it in this config file. The first behavior we want is sluggable. To enable it add orm:
- because we're using the Doctrine ORM:
Show Lines
|
// ... lines 1 - 2 |
stof_doctrine_extensions: | |
default_locale: en_US | |
orm: | |
Show Lines
|
// ... lines 6 - 8 |
and then default:
, because we want to enable this on our default entity manager. That's... really not important except in edge cases where you have multiple database connections.
Show Lines
|
// ... lines 1 - 2 |
stof_doctrine_extensions: | |
default_locale: en_US | |
orm: | |
default: | |
Show Lines
|
// ... lines 7 - 8 |
Then, sluggable: true
.
Show Lines
|
// ... lines 1 - 2 |
stof_doctrine_extensions: | |
default_locale: en_US | |
orm: | |
default: | |
sluggable: true |
That's it! Well... sort of. This won't make any real difference in our app yet. But, internally, the sluggable feature is now active.
Before we start using it, in QuestionFactory
, remove the code that sets the slug. I'll delete this logic, but keep an example function for later.
Show Lines
|
// ... lines 1 - 20 |
final class QuestionFactory extends ModelFactory | |
{ | |
Show Lines
|
// ... lines 23 - 40 |
protected function initialize(): self | |
{ | |
// see https://github.com/zenstruck/foundry#initialization | |
return $this | |
//->afterInstantiate(function(Question $question) { }); | |
; | |
} | |
Show Lines
|
// ... lines 48 - 52 |
} |
Now, temporarily, if we reload our fixtures with:
symfony console doctrine:fixtures:load
Yep! A huge error because slug
is not being set.
The @Gedmo\Slug
Annotation
So how do we tell the Doctrine extensions library that we want the slug
property to be set automatically? The library works via annotations. In the Question
entity, above the slug
property, add @Gedmo\Slug()
- making sure to autocomplete this so that PhpStorm adds the use
statement for this annotation.
The @Gedmo\Slug
annotation has one required option called fields={}
. Set it to name
.
Show Lines
|
// ... lines 1 - 6 |
use Gedmo\Mapping\Annotation as Gedmo; | |
Show Lines
|
// ... lines 8 - 11 |
class Question | |
{ | |
Show Lines
|
// ... lines 14 - 25 |
/** | |
* @ORM\Column(type="string", length=100, unique=true) | |
* @Gedmo\Slug(fields={"name"}) | |
*/ | |
private $slug; | |
Show Lines
|
// ... lines 31 - 131 |
} |
Done! The slug
will now be automatically set right before saving to a URL-safe version of the name
property.
Back at the terminal, try the fixtures now:
symfony console doctrine:fixtures:load
No errors! And on the homepage... yes! The slug looks perfect. We now never need to worry about setting the slug manually.
Doctrine's Event System
Internally, this magic works by leveraging Doctrine's event or "hook" system. The event system makes it possible to run custom code at almost any point during the "lifecycle" of an entity. Basically, you can run custom code right before or after an entity is inserted or updated, right after an entity is queried for or other times. You do this by creating an event subscriber or entity listener. We do have an example of an entity listener in our API Platform tutorial if you're interested.
Next, let's add two more handy fields to our entity: createdAt
and updatedAt
. The trick will be to have something automatically set createdAt
when the entity is first inserted and updatedAt
whenever it's updated. Thanks to Doctrine extensions, you're going to love how easy this is.
36 Comments
Hi!
Just an update - this should not be a problem anymore - we've been waiting for 2 new releases (from doctrine/extensions and stof-doctrine-extensions-bundle) and both have now been released. 🎉
Cheers!
This fails due to data fixtures when I try it.
Hey Aaron, could you tell me what error you got?
Hey Diego, sure here's the full composer output:
`./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Restricting packages listed in "symfony/symfony" to "5.1.*"
Your requirements could not be resolved to an installable set of packages.
Problem 1
- doctrine/data-fixtures 1.4.4 requires doctrine/common ^2.13|^3.0 -> satisfiable by doctrine/common[2.13.0, 2.13.1, 2.13.2, 2.13.3, 2.13.x-dev, 3.0.0, 3.0.1, 3.0.2, 3.0.x-dev, 3.1.x-dev] but these conflict with your requirements or minimum-stability.
- doctrine/data-fixtures 1.4.4 requires doctrine/common ^2.13|^3.0 -> satisfiable by doctrine/common[2.13.0, 2.13.1, 2.13.2, 2.13.3, 2.13.x-dev, 3.0.0, 3.0.1, 3.0.2, 3.0.x-dev, 3.1.x-dev] but these conflict with your requirements or minimum-stability.
- doctrine/data-fixtures 1.4.4 requires doctrine/common ^2.13|^3.0 -> satisfiable by doctrine/common[2.13.0, 2.13.1, 2.13.2, 2.13.3, 2.13.x-dev, 3.0.0, 3.0.1, 3.0.2, 3.0.x-dev, 3.1.x-dev] but these conflict with your requirements or minimum-stability.
- Installation request for doctrine/data-fixtures (locked at 1.4.4) -> satisfiable by doctrine/data-fixtures[1.4.4].
Installation failed, reverting ./composer.json to its original content.
exit status 2`
Oh, I think you only have to update the "doctrine/data-fixtures" library and it should do the trick
I did composer update but data-fixtures didn't have any updates. Trying again resulted in the same error.
Hmm, interesting... have you tried this composer require doctrine/common --update-with-dependencies
Just got time to test this morning and it looks like the issue with the extensions bundle has been addressed.
those are excellent news! Let's keep rocking
Hi Marcus S.!
Yes, thanks for sharing! Doctrine recently released a few new major versions (doctrine/common 3 and doctrine/persistence 2) and it's temporarily wreaking a small bit of havoc on things :p.You described it very well and the solution.
Thanks!
Hi there :)
I have a problem. I only needs to generate a Slugg if the private field $slugManual == false
How can i do it?
`/**
- @var string
* - @ORM\Column(name="prod_permalink", type="string", length=255, nullable=false)
- @Gedmo\Slug(fields={"name"}) <b>// Only if slugManual is false.</b>
*/
private $slug;
/**
- @var bool
* - @ORM\Column(name="prod_permalink_manual", type="boolean", nullable=false)
*/
private $slugManual = false;
`
Hey Leszek C.!
Hmm! I'm not sure this is easily possible. There is a system in this called slug handlers - https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/sluggable.md#slug-handlers - but i've never used them and I'm not sure if they will accomplish what you're looking for. But, it's worth a try. It looks like you need to:
A) make a class that implements SlugHandlerInterface: https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/src/Sluggable/Handler/SlugHandlerInterface.php
B) Configure this on your slug field - https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/sluggable.md#slug-handlers
C) Finally, in your "slug handler" class, I think you need to do nothing in each method EXCEPT for onSlugCompletion
. In this method, use the $object
variable - which will be your entity object - and check $slugManual
. If you do NOT want a slug generated, set the $slug
argument to null
. That argument is passed "by reference" so simply setting it to null will change that variable where it's used... and I think that it will cause the $slug to be set to null.
So... give it a try and let me know if it works!
Cheers!
Hello
I have tried the updated as well but still i am getting following errors -
C:\wamp64\www\Symfony001\Symfony-Doctrine>composer require stof/doctrine-extensions-bundle
Using version ^1.6 for stof/doctrine-extensions-bundle
./composer.json has been updated
Running composer update stof/doctrine-extensions-bundle
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires stof/doctrine-extensions-bundle ^1.6 -> satisfiable by stof/doctrine-extensions-bundle[v1.6.0].
- stof/doctrine-extensions-bundle v1.6.0 requires symfony/config ^4.4 || ^5.2 -> found symfony/config[v4.4.0, ..., v4.4.30, v5.2.0, ..., v5.3.4] but the package is fixed to v5.1.11 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
what else i am missing or can try?
thanks
Hey Jayant,
Did you try to the solution from the error message? I.e. try to run that command with --with-all-dependencies option:
$ composer require stof/doctrine-extensions-bundle --with-all-dependencies
This should help. Otherwise, requiring a lower version of stof/doctrine-extensions-bundle may help to, you can specify a specific version like this:
$ composer require "stof/doctrine-extensions-bundle:^1.5"
Btw, we use v1.5.0 in this course, so this should definitely work :)
Cheers!
Installing the Stof extensions bundle gives errors because you need at least Symfony 5.2 and this project is on 5.1.
Upgrade Symfony first, here is the docs for that: https://symfony.com/doc/cur...
Hey Sherri,
Yeah, the latest v1.6.0 of the bundle requires Symfony 5.2, but you can install the previous one v1.5.0 instead that fits for ^5.0. You can do it:
$ composer require "stof/doctrine-extensions-bundle:^1.5.0"
Cheers!
Hi, i saw the same problem like Aaroon kincer
the problem is
composer require stof/doctrine-extensions-bundle
<blockquote>Using version ^1.6 for stof/doctrine-extensions-bundle
./composer.json has been updated
Running composer update stof/doctrine-extensions-bundle
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires stof/doctrine-extensions-bundle ^1.6 -> satisfiable by stof/doctrine-extensions-bundle[v1.6.0].
- stof/doctrine-extensions-bundle v1.6.0 requires symfony/config ^4.4 || ^5.2 -> found symfony/config[v4.4.0, ..., v4.4.22, v5.2.0, ..., v5.2.7] but the package is fixed to v5.1.2 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
</blockquote>
If i have to upgrade symfony version then could you please tell me how can i upgrade it.
I am using symfony 5.1.2 version.
By the way thanks for this helpful tutorial.
Hey Monoranjan,
Yeah, that's because your Symfony version is 5.1... you should upgrade it to 5.2 first and then you should be able to install that latest 1.6 version of stof/doctrine-extensions-bundle . For Symfony 5.1 you can only install v1.5.0 of stof/doctrine-extensions-bundle that allows ^5.0 for Symfony components, but if you want the latest v1.6 - the only way to install it - upgrade Symfony to 5.2. Thankfully, it should be a minor easy upgrade.
Cheers!
Hello, I got this error message while intel the stop doctrine. can someone help me please? my head will explode in a minute :-(
➜ sites git:(chapter01) ✗ composer require doctrine/common:^2.7 --update-with-dependencies./composer.json has been updated
Running composer update doctrine/common --with-dependencies
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- doctrine/orm is locked to version 2.8.3 and an update of this package was not requested.
- doctrine/orm 2.8.3 requires doctrine/common ^3.0.3 -> found doctrine/common[3.0.3, 3.1.0, 3.1.1, 3.1.2] but it conflicts with your root composer.json require (^2.7).
Problem 2
- doctrine/orm 2.8.3 requires doctrine/common ^3.0.3 -> found doctrine/common[3.0.3, 3.1.0, 3.1.1, 3.1.2] but it conflicts with your root composer.json require (^2.7).
- doctrine/doctrine-fixtures-bundle 3.4.0 requires doctrine/orm ^2.6.0 -> satisfiable by doctrine/orm[2.8.3].
- doctrine/doctrine-fixtures-bundle is locked to version 3.4.0 and an update of this package was not requested.
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
Hey Jorg,
Hm, looks like you have doctrine/orm installed that is locked on 2.8.3 and that requires to have doctrine/common 3.0.3 or higher. To install the doctrine/common:^2.7 you should either downgrade the doctrine/orm to ^2.7... or require "doctrine/common^2.8.3" at least that will fit doctrine/orm version you have installed.
I hope this helps!
Cheers!
Hello Victor,
yes help so that I have an new error :-D
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires stof/doctrine-extensions-bundle ^1.6 -> satisfiable by stof/doctrine-extensions-bundle[v1.6.0].
- stof/doctrine-extensions-bundle v1.6.0 requires symfony/config ^4.4 || ^5.2 -> found symfony/config[v4.4.0, ..., v4.4.20, v5.2.0, ..., v5.2.4] but these were not loaded, likely because it conflicts with another require.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
Hey Jorg,
Hm, could you tell me what command exactly do you run? :) Btw, what Symfony version do you have? Looks like the stof/doctrine-extensions-bundle^1.6 requires symfony/config ^4.4 || ^5.2 - if you have a lower version, you would probably need to upgrade symfony/config first.
I hope this helps!
Cheers!
composer require stof/doctrine-extensions-bundle --with-all-dependencies
Using version ^1.6 for stof/doctrine-extensions-bundle
./composer.json has been updated
Running composer update stof/doctrine-extensions-bundle --with-all-dependencies
Loading composer repositories with package information
Restricting packages listed in "symfony/symfony" to "5.1.*"
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires stof/doctrine-extensions-bundle ^1.6 -> satisfiable by stof/doctrine-extensions-bundle[v1.6.0].
- stof/doctrine-extensions-bundle v1.6.0 requires symfony/config ^4.4 || ^5.2 -> found symfony/config[v4.4.0, ..., v4.4.20, v5.2.0, ..., v5.2.4] but it conflicts with your root composer.json require (5.1.*).
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
Hey Jörg daniel F.
I'm afraid that Victor is right and you'll have to upgrade Symfony5.1 to Symfony5.2 in order to install that bundle
Hi Folks,
i solved my problem with this site
https://symfony.com/doc/cur...
with this I make a Symfony upgrade to 5.2 and voila it works.
Thx @ all for your help
Wonderful!
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'slug' cannot be null
I'm still getting this error afterupdating the QuestionFactory and adding @Gedmo\Slug(fields={"name"})
any idea what could be wrong? I had to create the stof_doctrine_extensions.yaml file manually as stof/doctrine-extensions-bundle didn't install with the options as in the lesson.
Hey Will T.!
Hmmm. This definitely feels like the feature is simply not working at all. You mentioned that the stof_doctrine_extensions.yaml file was not added for you. This IS part of the recipe for this bundle - https://github.com/symfony/recipes-contrib/blob/master/stof/doctrine-extensions-bundle/1.2/config/packages/stof_doctrine_extensions.yaml
This make me think that the recipe did not execute. Possible reasons are that (A) you've disabled "contrib" recipes in your composer.json file - the extra.symfony.allow-contrib
key - or (B) you answered "No" when it asked you if you wanted to install it or (C) you're not using Flex (or D, something weird just happened, that's always possible).
Anyways, good job adding the stof_doctrine_extensions.yaml file manually. But also check that the bundle is enabled inside of config/bundles.php. That is one other possible thing that's going wrong.
Overall, the process "should" be pretty simple:
A) The bundle must be enabled
B) If the sluggable
behavior is enabled in your config file - https://symfonycasts.com/screencast/symfony-doctrine/sluggable#codeblock-82cb5aacdb - then the "SluggableListener" will be activated
C) Then, as long as you have the @Gedmo\Slug (which you do), then the magic will happen
If you were missing (A) or (B), then it would silently not work. Any other problems - like types in the annotation, etc - should result in a clear exception.
Let me know if you find out anything! Also, though it's rarely needed, when super weird things happen, you can try to run bin/console cache:clear
.
Cheers!
Hey Ryan!
I fixed this myself after looking through the bundles.php directory.
I found that Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true] was missing, so I added it.
Thanks for your thorough answer, though :)
Hey Will T.!
Nice debugging and I'm just glad you got it working :).
Cheers!
It would be great if you showed how to use translatable and sluggable together!
e.g. a product has a "name", this is usually used as a slug in the frontend
If you have a translated "name", you also want a translated "slug".
and in the frontend you would like to load the product data with a url like this (example):
/{_locale}/product/{slug}
DoctrineExtension does a good job. I just think that the parameter converter needs some special tweeking that might be quite interesting!
Hey Daniel K. !
Hmm, cool suggestion!
DoctrineExtension does a good job. I just think that the parameter converter needs some special tweeking that might be quite interesting!
Yes, I think you're right about this... and I think you would need a custom param converter to do the job. Well, you could add @ParamConverter
annotations, but that's annoying ;). Well, actually, I would create a custom "argument resolver" - not a param converter, just because that whole system was written before the "argument resolver" system, which is now much easier. We talk about that topic in general in the deep dive course: https://symfonycasts.com/screencast/deep-dive/global-args#custom-argumentvalueresolver
Making an argument resolver work would be fairly easy - you could, for example, look specifically to see if there is a $request->attributes->get('slug')
and, if there is, use $request->getLocale()
and that value to query for the entity. The ArgumentMetadata $argument
that's passed to your custom argument value resolver contains the type-hinted class name for each argument via ->getType()
. You could use that in supports()
to know if your argument resolver should operate on an argument (easiest way would be to have a list of classes you want to support, harder would be to check to see if the class is an entity in Doctrine or not) and then use it again in resolve
to fetch the repository and make the query.
That's a quick answer to a not-so-easy task. If you're interested, let me know if you need more details :).
Cheers!
In the "resolve" part: you query for the object. What if there is none. e.g. "getOneOrNullResult" returns "null"?
How do you throw a 404?
if the resolver yields a "null" value a 500 server error is thrown, because the controller expects an instance of the entity
So would you throw a NotFoundHttpException right in the Argument Resolver?
Hey Daniel K.
Yes, as far as I know that's what you have to do to trigger a 404 page. It's something similar as when you type-hint for an entity argument in your controller's method, it will query it for you or throw a `NotFoundHttpException` which will trigger a 404 page
Cheers!
Thank you for this hint and the great idea! I tried it and it seems to me that the ParamConverter kicks in before the argumentValueResolver.So the "auto_convert" option of the param converter needs to be set to false. But of course i'd like to use it for other requests :)
Any thoughts on this?
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.4.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^2.1", // 2.1.1
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.7", // 2.8.2
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.0
"sensio/framework-extra-bundle": "^6.0", // v6.2.1
"sentry/sentry-symfony": "^4.0", // 4.0.3
"stof/doctrine-extensions-bundle": "^1.4", // v1.5.0
"symfony/asset": "5.1.*", // v5.1.2
"symfony/console": "5.1.*", // v5.1.2
"symfony/dotenv": "5.1.*", // v5.1.2
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/framework-bundle": "5.1.*", // v5.1.2
"symfony/monolog-bundle": "^3.0", // v3.5.0
"symfony/stopwatch": "5.1.*", // v5.1.2
"symfony/twig-bundle": "5.1.*", // v5.1.2
"symfony/webpack-encore-bundle": "^1.7", // v1.8.0
"symfony/yaml": "5.1.*", // v5.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.0.4
"twig/twig": "^2.12|^3.0" // v3.0.4
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.1.*", // v5.1.2
"symfony/maker-bundle": "^1.15", // v1.23.0
"symfony/var-dumper": "5.1.*", // v5.1.2
"symfony/web-profiler-bundle": "5.1.*", // v5.1.2
"zenstruck/foundry": "^1.1" // v1.5.0
}
}
If you are running into an error with messages like
Can only install one of: doctrine/common[v2.9.0, 3.0.2].
while installing the stof-bundle, it is most likely because your doctrine version is too new. At the time of writing, the orm bundle installs doctrine 3.0.2, but the stof bundle requires a 2.x version of doctrine. Thus, the dependency cannot be fulfilled and the installation breaks. To be able to install the bundle nevertheless, you will have to downgrade doctrine. Use the following:symfony composer require doctrine/common:^2.7 --update-with-dependencies
After that, you should be able to install the stof doctrine extension bundle