Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

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.

Start your All-Access Pass
Buy just this tutorial for $6.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The 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 service markdown.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.

Leave a comment!

34
Login or Register to join the conversation
David R. Avatar
David R. Avatar David R. | posted 4 years ago

Hello,
How did you get the auto-completion inside the Yaml file?

Thanks :)

Reply

Hi David R.

Lastest PhpStorm with activated Symfony plugin allows us to use autocomplete in yaml files.

Cheers!

Reply
Default user avatar
Default user avatar jian su | posted 4 years ago | edited

there If transformer is not passing markdown_transformer object into construct since we are using services in our services.yml. Can I safely remove construct from our MarkdownTransformer class?

Reply

Hey jian su!

Good question. You *cannot* remove it, and for a very important reason :). In services.yml, thanks to arguments: ['@markdown.parser'], when we call $this->get('app.markdown_transformer'), behind the scenes, the container IS instantiating our MarkdownTransformer and it IS passing it the markdown.parser as the first argument. Basically, imagine that, behind the scenes, this is happening (because it IS!):


new MarkdownTransformer($this->container->get('markdown.parser'));

So even though we are not explicitly passing the argument to ourselves, the container *is* still passing this. So, it's definitely needed.

Does that make sense?

Reply
Default user avatar

Make total Sense~! Services is awesome, it did a lot of dirty work behind the scenes! But The type hint things is kinda suck since I need to dig deep and find the base class, and then even i found it, there are multiple implements such as Cache, FlushableCache, ClearableCache, MultiGetCache, MultiPutCache. Even I am luck I found the Cache.. it also implement mutiple places...and then I need to find the one has save() and contains... you know what i mean.. any better way?

Reply
Default user avatar

Hello
On the last step when I refresh
http://localhost:8000/genus/Balaena

I have error
Attempted to load class "MarkdownTransformer" from namespace "AppBundle\Service".
Did you forget a "use" statement for another namespace?

Stack Trace
in var\cache\dev\appDevDebugProjectContainer.php at line 300
protected function getApp_MarkdownTransformerService()
{
return $this->services['app.markdown_transformer'] = new \AppBundle\Service\MarkdownTransformer($this->get('markdown.parser'));
}

How could I to fix this?

Reply

Hey Nina

I believe you just have a tiny problem with your use statemets, or you forgot to import it or you chose the wrong type. Give it a look.
If you can't fix it, show me the code where you use the MarkdownTransformer, so I can give it a look for you

Cheers!

Reply
Default user avatar
Default user avatar Misha Karas | posted 4 years ago

You used app.markdown_transformer as service name. I don't really understand why you specify app before markdown_transformer. Is this required or just optional?

Reply

Hey Misha!

The name you give to your services is totally up to you, but symfony has some conventions for it's community to follow
You can get more info about it here:
http://symfony.com/doc/curr...

Cheers!

Reply
Default user avatar
Default user avatar Marco Koopman | posted 4 years ago

And what if I want to get a repository and I need the Entity manager in my service?
I don't know what to give as argument in the services.yml

The code:

// the controller \\

$em = $this->getDoctrine()->getManager();
$transformer = new MarkdownTransformer($em);

// the service \\

private $em;

public function __construct(EntityManager $em)
{
$this->em = $em;
}

// services.yml \\

app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['??']

UPDATE:

Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. Try changing the type-hint for argument "$em" of method "AppBundle\Service\MarkdownTransformer::__construct()" to one of its parents: interface "Doctrine\ORM\EntityManagerInterface", or interface "Doctrine\Common\Persistence\ObjectManager".

https://symfony.com/doc/cur...

Not sure what this all means though but it seems that there is a new way of registering your service.

Reply

Hey Marco Koopman

What you are doing it's ok, whenever your services depend on other services, you can inject them by type-hinting it's namespace, but if they implement an interface, it's better (and soon it will be obligatory) to type-hint the interface.

In Symfony 3.3 the way how you define your services changed, the class namespace is the service's name now, you can dive deeper by watching our dedicated tutorial for Symfony 3.3 - https://knpuniversity.com/s...

Have a nice day!

Reply

You said that service in the container is created only once, but in the `getApp_MarkdownTransformerService` function doesn't seem to check if the object was created before. How does container ensure that only one instance is created?

Reply

Hey boykodev

Great question!
If you look closely, that method `getApp_MarkdownTransformerService` is protected, so it gets called internally by the container, if you go to the Container class and check for "get()" method, you will see that it first checks if the service was already requested and if not, it will try to create an instance of it

Cheers!

Reply

Thanks for the clarification!

Reply

NP! you are welcome :)

Reply
Default user avatar

Is there a reason why my phpStorm doesn't auto-suggest the $this->get() function [or even $this->container->get()] when I type? Seems like other auto-suggests are working fine but this guy isn't...

Thanks!

Reply
MolloKhan Avatar MolloKhan | SFCASTS | Goz | posted 4 years ago | edited

Hey Goz

Have you activated Symfony's plugin? If that's the case, you may need to exclude `var/cache` folder

Let me know if that doesn't work

Have a nice day

Reply
Default user avatar
Default user avatar Goz | MolloKhan | posted 4 years ago | edited

Hi MolloKhan - thanks for the tip but yeah, already activated the plugin and excluded that folder. Other auto-suggests work fine... just not $this->

$this only autosuggests functions present in the Controller file itself (listAction(), newAction() etc.)

Reply

Yo Goz !

Hmm, interesting! What class is your controller extending? I'm asking because starting in Symfony 3.4, there is the normal Controller class but also an AbstractController. Both are *basically* the same, but I thought I'd check in case it makes a difference.

Side note: in Symfony 4, we don't actually use $this->get() anymore. Instead, we use autowiring to fetch services into our controller. So, this might be a temporary problem for you :). Check out https://knpuniversity.com/s...

Cheers!

Reply
Default user avatar

Hi weaverryan !

I'm using the exact starter code from the KNPU download zip for this course module, so I have to use their versions (by executiing `composer update` ) so it's symf 3.1.x.

I presumed that was the same version used when KNPU created the video above - so it's weird that I don't get the same autosuggests as they do, isn't it?

Reply

Hmm, it may be related to your PhpStorm version, which one are you using?
Oh, you can try re-indexing your project in PhpStorm

Reply
Default user avatar

Latest version build of 2017.3
How does one re-index a project?

Reply

Open settings, then go to: languages & frameworks -> PHP -> Symfony
at the upper right corner is a button that says "Clear index", thats the one

Sometimes my auto completion gets broken and I have to run "composer install", maybe it could work for you

Cheers!

Reply
Default user avatar

Thanks - tried that (and `composer install` - still no luck. Weird.

Reply

Weird indeed, I'm running out of options, do you have this same problem with other projects?

What about if you remove your project from PhpStorm and generate it again.
Also, you could try re-installing Symfony's plugin

Reply
Default user avatar

I just cleared out my app folder and loaded the next KNPU coursework bundle and of course - now I have autosuggest working for $this->get() etc. ! I'll put this down to "I did something weird and now it's working" :)

Thanks for trying to help though - i appreciate it!

Reply

Ohh I hate when that happens, but I'm glad to know that you could fix your problem :)

Cheers!

Reply
Default user avatar

Doesn't work in 3.3.6, fix code:


app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser']
public: yes
Reply

Hey Mike,

Thanks for sharing it! Btw, you probably missed it but we had added a note to a few tutorials explaining how to prevent getting this kind of errors when you are on Symfony 3.3 and newer: http://knpuniversity.com/bl... - I think it could be useful for others too.

Cheers!

Reply
Default user avatar
Default user avatar Helene Shaikh | posted 4 years ago

When you look for all services in "debug:container markdown", you can find "markdown.parser" in the list of services. So why is it still necessary to register it in services.yml?

Reply

Hey Helene Shaikh!

I believe you are confusing "markdown.parser" service with our own service "app.markdown_transformer". Our service needs or depends on "markdown.parser", so that's why we are injecting it as an argument.

I hope it helps you clarifying things a bit :)

Have a nice day

Reply
Default user avatar
Default user avatar Jesus Gabriel Jimenez Blanco | posted 4 years ago

Hello! i'm getting this error:

Type error: Argument 1 passed to
AppBundle\Service\MarkdownTransformer::__construct() must be an instance
of AppBundle\Service\MarkdownParserInterface, instance of
Knp\Bundle\MarkdownBundle\Parser\Preset\Max given, called in
C:\xampp\htdocs\Symfony\aqua_note\var\cache\dev\appDevDebugProjectContainer.php
on line 292

It's like it can't cast from Max instance given when called the contruct function to the MarkdownParserInterface even though Max implements MarkdownParserInterface... I don't understand why does it happen, it also happened in the previous page... I tried to solve it myself but it's just like you can't actually cast between those two...

If anyone could help I would really appreciate it. Thanks in advance!

Reply

Hey Jesus!

Oh man, I know this error! And it's *super* hard to debug the first time you see it.

First, the solution: you're missing the "use" statement in MarkdownTransformer for the MarkdownParserInterface.

Now, the explanation :). Because the "use" statement is missing, PHP assumes that MarkdownParserInterface must be a class that lives in the same namespace as the class that it's currently inside of. In other words, it assumes that the class is AppBundle\Service\MarkdownParserInterface. Then, the error "kind of" makes sense, it's saying:

> Hey! This object, which is an instance of Max, is being passed to __construct(), but you're expecting some (non-existent) MarkdownParserInterface instance!

Technically, you *really* want PHP to yell: "Yo! What is this MarkdownParserInterface!? That's not a real class?". But, it doesn't.

So easy fix, but I give it a long explanation because this is *such* a tough one to debug, and - hopefully - next time you'll debug it immediately.

I hope that helps! Or, if I'm *completely* wrong, you can tell me ;).

Cheers!

1 Reply
Default user avatar
Default user avatar Jesus Gabriel Jimenez Blanco | weaverryan | posted 4 years ago

That was it!
Thank you so much for the detailed explanation, that really helps becoming a better programmer!

I'm really enjoying this tutorial and it's so awesome that we can count on you whenever there are errors!
Thank you again!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}