Bundle Configuration Class
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 KnpUIpsum
class has two constructor args, but the user can't control these... yet. In knpu_lorem_ipsum.yaml
, here's my idea: allow the user to use two new config keys, like unicorns_are_real
and min_sunshine
, and pass those values to our service as arguments.
Comment-out the var_dump
. Symfony's configuration system is smart: all the keys are validated. If you typo a key - like secret2
under framework
, when you refresh, you get a big ol' error! Yep, each bundle creates its own "tree" of all the valid config keys.
In fact, find your terminal. Run:
php bin/console config:dump framework
This is an example of the entire tree of valid configuration for framework
! This is amazing, and it's made possible by a special Configuration
class. It's time to create our own!
Creating the Configuration Class
Inside the DependencyInjection
directory, create a new class called Configuration
. Make this implement ConfigurationInterface
: the one from the Config
component. We'll need to implement one method: go to the Code -> Generate menu, or Cmd
+N
on a Mac, select "Implement Methods" and choose getConfigTreeBuilder()
.
// ... lines 1 - 6 | |
class Configuration implements ConfigurationInterface | |
{ | |
public function getConfigTreeBuilder() | |
{ | |
} | |
} |
This is one of the strangest classes you'll ever see. By using PHP code, we're going to define the entire tree of valid config that can be passed to our bundle.
A great way to see how this class works is to look at an existing one! Type Shift+Shift to open a class called FrameworkExtension
, deep in the core of Symfony. Yep, this is the extension class for FrameworkBundle! It has the same load()
method as our extension.
In the same directory, if you click on the top tree, you'll find a class called Configuration
. Inside, it defines all of the valid config keys with a, sort of, nested tree format. This is a super powerful and, honestly, super complex system. We're only going to use a few basic features. If you need to define a more complex config tree, definitely steal, um, borrow, from these core classes.
Building the Config Tree
Back in our class, start with $treeBuilder = new TreeBuilder()
. Then, $rootNode = $treeBuilder->root()
and pass the name of our key: knpu_lorem_ipsum
.
// ... lines 1 - 9 | |
public function getConfigTreeBuilder() | |
{ | |
$treeBuilder = new TreeBuilder(); | |
$rootNode = $treeBuilder->root('knpu_lorem_ipsum'); | |
// ... lines 14 - 21 | |
} |
Tip
Since Symfony 4.3 you should pass the root node name to the TreeBuilder
instead:
$treeBuilder = new TreeBuilder('knpu_lorem_ipsum');
$rootNode = $treeBuilder->getRootNode();
// ...
Now... just start building the config tree! $rootNode->children()
, and below, let's create two keys. The first will be for the "unicorns are real" value, and it should be a boolean. To add that, say ->booleanNode('unicorns_are_real')
, ->defaultTrue()
and to finish configuring this node, ->end()
.
// ... lines 1 - 9 | |
public function getConfigTreeBuilder() | |
{ | |
// ... lines 12 - 13 | |
$rootNode | |
->children() | |
->booleanNode('unicorns_are_real')->defaultTrue()->end() | |
// ... lines 17 - 21 | |
} |
The other option will an integer: ->integerNode('min_sunshine')
, default it to 3, then ->end()
. Call ->end()
one more time to finish the children()
.
// ... lines 1 - 13 | |
$rootNode | |
->children() | |
// ... line 16 | |
->integerNode('min_sunshine')->defaultValue(3)->end() | |
->end() | |
; | |
// ... lines 20 - 23 |
Weird, right!? Return the $treeBuilder
at the bottom.
// ... lines 1 - 9 | |
public function getConfigTreeBuilder() | |
{ | |
// ... lines 12 - 20 | |
return $treeBuilder; | |
} |
Using the Configuration Class
In our extension, we can use this to validate and merge all the config together. Start with $configuration = $this->getConfiguration()
and pass this $configs
and the container. This simply instantiates the Configuration
class.
// ... lines 1 - 9 | |
class KnpULoremIpsumExtension extends Extension | |
{ | |
public function load(array $configs, ContainerBuilder $container) | |
{ | |
// ... lines 14 - 16 | |
$configuration = $this->getConfiguration($configs, $container); | |
// ... lines 18 - 19 | |
} | |
// ... lines 21 - 25 | |
} |
Here's the really important part: $config = $this->processConfiguration()
: pass the configuration object and the original, raw array of $configs
. var_dump()
that final config and die
!
// ... lines 1 - 11 | |
public function load(array $configs, ContainerBuilder $container) | |
{ | |
// ... lines 14 - 17 | |
$config = $this->processConfiguration($configuration, $configs); | |
var_dump($config);die; | |
} | |
// ... lines 21 - 27 |
Let's see what happens! Find your browser and... refresh! We get an error... which is awesome! It says:
Unrecognized option "bar" under "knpu_lorem_ipsum"
This is telling us:
Yo! "bar" is not one of the valid config keys!
Back in knpu_lorem_ipsum.yaml
, temporarily comment-out all of our config. And, refresh again. Yes! No error! Instead, we see the final, validated & normalized config, with the two keys we created in the Configuration
class.
#knpu_lorem_ipsum: | |
# bar: true |
Put back the config, but use a real value: min_sunshine
set to 5.
knpu_lorem_ipsum: | |
min_sunshine: 5 |
Refresh one last time. Woohoo! min_sunshine
equals 5. These Configuration
classes are strange... but they take care of everything: validating, merging and applying default values.
Dynamically Setting the Arguments
We are finally ready to use this config. But... how? The service & its arguments are defined in services.xml
... so we can't just magically reference those dynamic config values here.
Copy the service id and go back to the extension class. That container builder holds the instructions on how to instantiate our service - like its class and what constructor arguments to pass to it. And we - right here in PHP - can change those.
Check it out: start with $definition = $container->getDefinition()
and pass the service id. This returns a Definition
object, which holds the service's class name, arguments and a bunch of other stuff. Now we can say $definition->setArgument()
: set the first argument - which is index 0 - to $config['']
. The first argument is $unicornsAreReal
. So use the unicorns_are_real
key. Set the second argument - index one - to min_sunshine
.
// ... lines 1 - 9 | |
class KnpULoremIpsumExtension extends Extension | |
{ | |
public function load(array $configs, ContainerBuilder $container) | |
{ | |
// ... lines 14 - 19 | |
$definition = $container->getDefinition('knpu_lorem_ipsum.knpu_ipsum'); | |
$definition->setArgument(0, $config['unicorns_are_real']); | |
$definition->setArgument(1, $config['min_sunshine']); | |
} | |
// ... lines 24 - 28 | |
} |
That's it! Go back and refresh! It works! Sunshine now appears at least 5 times in every paragraph. Our dynamic value is being passed!
Oh, and, bonus! In your terminal, run config:dump
again, but this time pass it knpu_lorem_ipsum
:
php bin/console config:dump knpu_lorem_ipsum
Yes! Our bundle now prints its config thanks to the Configuration
class. If you want to get really fancy - which of course we do - you can add documentation there as well. Add ->info()
and pass a short description about why you would set this. Do the same for min_sunshine
.
// ... lines 1 - 7 | |
class Configuration implements ConfigurationInterface | |
{ | |
public function getConfigTreeBuilder() | |
{ | |
// ... lines 12 - 13 | |
$rootNode | |
->children() | |
->booleanNode('unicorns_are_real')->defaultTrue()->info('Whether or not you believe in unicorns')->end() | |
->integerNode('min_sunshine')->defaultValue(3)->info('How much do you like sunshine?')->end() | |
// ... lines 18 - 21 | |
} | |
} |
Run config:dump
again:
php bin/console config:dump knpu_lorem_ipsum
Pretty, freakin' cool.
Next, let's get fancier with our config and allow entire services to be swapped out.
Hi,
There is a deprecation on the root node (<blockquote>@deprecated since Symfony 4.3, pass the root name to the constructor instead</blockquote>), it should be :
<br />$treeBuilder = new TreeBuilder('knpu_lorem_ipsum');<br />$rootNode = $treeBuilder->getRootNode();<br />
Thanks for this great tutorial !
Pierre