Register your Service in the Container
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 MarkdownTransformer
class is not an Autobot, nor a Decepticon, but it is a service because it does work for us. It's no different from any other service, like the markdown.parser
, logger
or anything else we see in debug:container
... except for one thing: it sees dead people. I mean, it does not live in the container.
Nope, we need to instantiate it manually: we can't just say something like $this->get('app.markdown_transformer')
and expect the container to create it for us.
Time to change that... and I'll tell you why it's awesome once we're done.
Open up app/config/services.yml
:
# Learn more about services, parameters and containers at | |
# http://symfony.com/doc/current/book/service_container.html | |
parameters: | |
# parameter_name: value | |
services: | |
# service_name: | |
# class: AppBundle\Directory\ClassName | |
# arguments: ["@another_service_name", "plain_value", "%parameter_name%"] |
Tip
If you're using Symfony 3.3, your app/config/services.yml
contains some extra code
that may break things when following this tutorial! To keep things working - and learn
about what this code does - see https://knpuniversity.com/symfony-3.3-changes
To add a new service to the container, you basically need to teach the container how to instantiate your object.
This file already has some example code to do this... kill it! Under the services
key, give your new service a nickname: how about app.markdown_transformer
:
// ... lines 1 - 5 | |
services: | |
app.markdown_transformer: | |
// ... lines 8 - 10 |
This can be anything: we'll use it later to fetch the service. Next, in order for the container to be able to instantiate this, it needs to know two things: the class name and what arguments to pass to the constructor. For the first, add class:
then the full AppBundle\Service\MarkdownTransformer
:
// ... lines 1 - 5 | |
services: | |
app.markdown_transformer: | |
class: AppBundle\Service\MarkdownTransformer | |
// ... lines 9 - 10 |
For the second, add arguments:
then make a YAML array: []
. These are the constructor arguments, and it's pretty simple. If we used [sunshine, rainbows]
, it would pass the string sunshine
as the first argument to MarkdownTransformer
and rainbow
as the second. And that would be a very pleasant service.
In reality, MarkdowTransformer
requires one argument: the markdown.parser
service. To tell the container to pass that, add @markdown.parser
:
// ... lines 1 - 5 | |
services: | |
app.markdown_transformer: | |
class: AppBundle\Service\MarkdownTransformer | |
arguments: ['@markdown.parser'] |
That's it. The @
is special: it says:
Woh woh woh, don't pass the string
markdown.parser
, pass the servicemarkdown.parser
.
And with 4 lines of code, a new service has been born in the container. I'm such a proud parent.
Go look for it:
./bin/console debug:container markdown
There is its! And it's so cute. Idea! Let's use it! Instead of new MarkdownTransformer()
, be lazier: $transformer = $this->get('app.markdown_transformer)
:
// ... lines 1 - 13 | |
class GenusController extends Controller | |
{ | |
// ... lines 16 - 58 | |
public function showAction($genusName) | |
{ | |
// ... lines 61 - 69 | |
$markdownTransformer = $this->get('app.markdown_transformer'); | |
// ... lines 71 - 97 | |
} | |
// ... lines 99 - 123 | |
} |
When this line runs, the container will create that object for us behind the scenes.
Why add a Service to the Container
Believe it or not, this was a huge step. When you add your service to the container, you get two great thing. First, using the service is so much easier: $this->get('app.markdown_transformer)
. We don't need to worry about passing constructor arguments: heck it could have ten constructor arguments and this simple line would stay the same.
Second: if we ask for the app.markdown_transformer
service more than once during a request, the container only creates one of them: it returns that same one object each time. That's nice for performance.
Oh, and by the way: the container doesn't create the MarkdownTransformer
object until and unless somebody asks for it. That means that adding more services to your container does not slow things down.
The Dumped Container
Ok, I have to show you something cool. Open up the var/cache
directory. If you don't see it - you may have it excluded: switch to the "Project" mode in PhpStorm.
Open var/cache/dev/appDevDebugProjectContainer.php
:
// ... lines 1 - 9 | |
/** | |
* appDevDebugProjectContainer. | |
* | |
* This class has been auto-generated | |
* by the Symfony Dependency Injection Component. | |
*/ | |
class appDevDebugProjectContainer extends Container | |
{ | |
// ... lines 18 - 3707 | |
} |
This is the container: it's a class that's dynamically built from our configuration. Search for "MarkdownTransformer" and find the getApp_MarkdownTransformerService()
method:
// ... lines 1 - 15 | |
class appDevDebugProjectContainer extends Container | |
{ | |
// ... lines 18 - 23 | |
public function __construct() | |
{ | |
// ... lines 26 - 32 | |
$this->methodMap = array( | |
// ... line 34 | |
'app.markdown_transformer' => 'getApp_MarkdownTransformerService', | |
// ... lines 36 - 238 | |
); | |
// ... lines 240 - 249 | |
} | |
// ... lines 251 - 272 | |
/** | |
* Gets the 'app.markdown_transformer' service. | |
* | |
* This service is shared. | |
* This method always returns the same instance of the service. | |
* | |
* @return \AppBundle\Service\MarkdownTransformer A AppBundle\Service\MarkdownTransformer instance. | |
*/ | |
protected function getApp_MarkdownTransformerService() | |
{ | |
return $this->services['app.markdown_transformer'] = new \AppBundle\Service\MarkdownTransformer($this->get('markdown.parser')); | |
} | |
// ... lines 285 - 3707 | |
} |
Ultimately, when we ask for the app.markdown_transformer
service, this method is called. And look! It runs the same PHP code that we had before in our controller: new MarkdownTransformer()
and then $this->get('markdown.parser')
- since $this
is the container.
You don't need to understand how this works - but it's important to see this. The configuration we wrote in services.yml
may feel like magic, but it's not: it causes Symfony to write plain PHP code that creates our service objects. There's no magic: we describe how to instantiate the object, and Symfony writes the PHP code to do that. This makes the container blazingly fast.
Hello,
How did you get the auto-completion inside the Yaml file?
Thanks :)