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 SubscribeI want to make two other changes to the new "word provider" setup. The first is optional: it's another common method for making the word provider configurable.
Go back into our services.xml
file. Right now, we set the first argument inside of the XML file, then override that argument in the extension class, if a different value is provided. Another option - and we'll talk about the advantages later - is to use a service alias.
Copy the alias we created earlier in order to enable autowiring. Create a new alias whose id is knpu_lorem_ipsum.word_provider
and set the alias to the knp_word_provider
service id above.
... lines 1 - 6 | |
<services> | |
... lines 8 - 13 | |
<service id="knpu_lorem_ipsum.word_provider" alias="knpu_lorem_ipsum.knpu_word_provider" public="false" /> | |
... line 15 | |
</services> | |
... lines 17 - 18 |
Thanks to this, there is now a new service in the container called knpu_lorem_ipsum.word_provider
. But when someone references it, it actually just points to our knpu_lorem_ipsum.knpu_word_provider
. Now, for the argument to KnpUIpsum
, pass the alias id instead.
... lines 1 - 7 | |
<service id="knpu_lorem_ipsum.knpu_ipsum" class="KnpU\LoremIpsumBundle\KnpUIpsum" public="true"> | |
<argument type="service" id="knpu_lorem_ipsum.word_provider" /> | |
</service> | |
... lines 11 - 18 |
So far, this won't change anything. But open the extension class. Instead of changing the argument, we can override the alias to point to their service id. Do this with $container->setAlias()
. First pass knpu_lorem_ipsum.word_provider
and set this alias to $config['word_provider']
. We don't need the new Reference()
here because the setAlias()
method expects this to be a service ID.
... lines 1 - 10 | |
class KnpULoremIpsumExtension extends Extension | |
{ | |
public function load(array $configs, ContainerBuilder $container) | |
{ | |
... lines 15 - 21 | |
if (null !== $config['word_provider']) { | |
$container->setAlias('knpu_lorem_ipsum.word_provider', $config['word_provider']); | |
} | |
... lines 25 - 26 | |
} | |
... lines 28 - 32 | |
} |
And before even trying it, copy the service alias, find your terminal, and run:
php bin/console debug:container --show-private knpu_lorem_ipsum.word_provider
Yes! This is an alias to our CustomWordProvider
. And that means that the first argument to KnpUIpsum
will use that. Refresh to make sure it still works. It does!
There's no amazing reason to use this alias strategy versus what we had before, but there are two minor advantages. First, if we needed to reference the word provider service in multiple places - probably in services.xml
- using an alias is easier, because you don't need to remember to, for example, replace 5 different arguments where the service is used. And second, if we wanted this service to be used directly by our users, creating an alias is the only way to give them a service id they can reference, even if they override the word provider to be something else.
Ok, our setup is really, really nice. But there is one restriction we're putting on our user that we really do not need to! Open KnpUIpsum
and scroll all the way to the constructor. The first argument is type-hinted with KnpUWordProvider
. This means that if the user wants to create their own word provider, they must extend our original KnpUWordProvider
. We are doing this... because we just want to add a new word to the list, but this should not be required! All we care about is that the service has a getWordList()
method that returns an array.
In other words, this is the perfect use-case for an interface! Wooo! In the bundle, create a new PHP class. Call it WordProviderInterface
and change the "kind" from class to interface.
Inside, add the getWordList()
method and make it return an array. This is also the perfect place to add some documentation about what this method should do.
... lines 1 - 4 | |
interface WordProviderInterface | |
{ | |
/** | |
* Return an array of words to use for the fake text. | |
* | |
* @return array | |
*/ | |
public function getWordList(): array; | |
} |
With the interface done, go back to KnpUIpsum
, change the type-hint to WordProviderInterface
. The user can now pass anything they want, as long as it has this getWordList()
method... because that is what we're using at the bottom of KnpUIpsum
.
... lines 1 - 9 | |
class KnpUIpsum | |
{ | |
... lines 12 - 17 | |
public function __construct(WordProviderInterface $wordProvider, bool $unicornsAreReal = true, $minSunshine = 3) | |
... lines 19 - 209 | |
} |
Then, of course, we also need to go open our provider and make sure it implements this interface: implements WordProviderInterface
.
... lines 1 - 4 | |
class KnpUWordProvider implements WordProviderInterface | |
{ | |
... lines 7 - 142 | |
} |
If you try it now... not broken! And yea, our CustomWordProvider
will still extend KnpUWordProvider
, but that's now optional - we could just implement the interface directly.
Next, let's take a big step and move our bundle out of our code and give it it's own composer.json
file!
Hey Capucine,
Not really, it's just a misprint :) Thanks for pointing this, I fixed it in https://github.com/knpunive...
Cheers!
One more comment:
If you implement the Interface in your CustomWordProvider, $words = parent::getWordList();
does not work longer because not extending a Service wich is the parent.
Hey Maik T.!
That's correct :). This is really up to the user at this point - they can choose to make their CustomWordProvider
continue to extend KnpUWordProvider
or only implement the interface. But yea, then they wouldn't be able to call the parent function :). If you didn't want to extend the internal class, the user could use "service decoration". That allows you to fetch the KnpUWordProvider "words" without extending the class - the most pure solution for whoever is implementing your bundle - https://symfonycasts.com/screencast/api-platform-security/service-decoration#service-declaration-amp-decoration
Cheers!
There is a logical Error in the script:
If you remove the Custom word Provider and refresh the site you will get a ServiceNotFoundException because the Interface is included but no Service is really set.
To fix it you can still fix it simply in the Extension class by changing following code:
`
if(!is_null($config['word_provider'])) {
$container->setAlias('knpu_lorem_ipsum.word_provider', $config['word_provider']);
} else {
$container->setAlias('knpu_lorem_ipsum.word_provider', 'knpu_lorem_ipsum.knpu_word_provider');
}
`
Hey Maik T.!
Hmm. Are you sure that's needed? The part you added was the "else", correct? That alias was already added in the services.xml file - https://symfonycasts.com/sc...
Or am I missing something? It's totally possible :).
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"doctrine/annotations": "^1.8", // v1.8.0
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knpuniversity/lorem-ipsum-bundle": "*@dev", // dev-master
"nexylan/slack-bundle": "^2.0,<2.2", // v2.0.1
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.1.6
"symfony/asset": "^4.0", // v4.0.6
"symfony/console": "^4.0", // v4.0.6
"symfony/flex": "^1.0", // v1.18.7
"symfony/framework-bundle": "^4.0", // v4.0.6
"symfony/lts": "^4@dev", // dev-master
"symfony/twig-bundle": "^4.0", // v4.0.6
"symfony/web-server-bundle": "^4.0", // v4.0.6
"symfony/yaml": "^4.0", // v4.0.6
"weaverryan_test/lorem-ipsum-bundle": "^1.0" // v1.0.0
},
"require-dev": {
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"sensiolabs/security-checker": "^4.1", // v4.1.8
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.6
"symfony/dotenv": "^4.0", // v4.0.6
"symfony/maker-bundle": "^1.0", // v1.1.1
"symfony/monolog-bundle": "^3.0", // v3.2.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.3
"symfony/stopwatch": "^3.3|^4.0", // v4.0.6
"symfony/var-dumper": "^3.3|^4.0", // v4.0.6
"symfony/web-profiler-bundle": "^3.3|^4.0" // v4.0.6
}
}
At the end of the script :
CustomUserProvider ? I missed something ?