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

The Dreaded Dependency Injection

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

The MarkdownTransformer will do two things: parse markdown and eventually cache it. Let's start with the first.

Open up GenusController and copy the code that originally parsed the text through markdown. Now... paste this into the parse function, return that and update the variable to $str. Wow, that was easy:

... lines 1 - 4
class MarkdownTransformer
{
public function parse($str)
{
return $this->get('markdown.parser')
->transform($str);
}
}

Go back and refresh. It explodes!

Attempted to call an undefined method named "get" on MarkdownTransformer

Forget about Symfony: this makes sense. The class we just created does not have a get() function and it doesn't extend anything that would give this to us.

In a controller, we do have this function. But more importantly, we have access to the container, either via that shortcut method or by saying $this->container. From there we can fetch any service by calling ->get() on it.

But that's special to the controller: as soon as you're not in a controller - like MarkdownTransformer - you don't have access to the container, its services... or anything.

The Dependency Injection Flow

So here's the quadrillion bitcoin question: how can we get access to the container inside MarkdownTransformer? But wait, we don't really need the whole container: all we really need is the markdown parser object. So a better question is: how can we get access to the markdown parser object inside MarkdownTransformer?

The answer is probably the scariest word that was ever created for such a simple idea: dependency injection. Ah, ah, ah. I think someone invented that word just to be a jerk... especially because it's so simple...

Here's how it goes: whenever you're inside of a class and you need access to an object that you don't have - like the markdown parser - add a public function __construct() and add the object you need as an argument:

... lines 1 - 4
class MarkdownTransformer
{
... lines 7 - 8
public function __construct($markdownParser)
{
... line 11
}
... lines 13 - 18
}

Next create a private property and in the constructor, assign that to the object: $this->markdownParser = $markdownParser:

... lines 1 - 4
class MarkdownTransformer
{
private $markdownParser;
public function __construct($markdownParser)
{
$this->markdownParser = $markdownParser;
}
... lines 13 - 18
}

Now that the markdown parser is set on the property, use it in parse(): get rid of $this->get() and just use $this->markdownParser:

... lines 1 - 4
class MarkdownTransformer
{
... lines 7 - 13
public function parse($str)
{
return $this->markdownParser
->transform($str);
}
}

We're done! Well, done with this class. You see: whoever instantiates our MarkdownTransformer will now be forced to pass in a markdown parser object.

Of course now we broke the code in our controller. Yep, in GenusController PhpStorm is angry: we're missing the required $markdownParser argument in the new MarkdownTransformer() call. That's cool - because now that we're in the controller, we do have access to that object. Pass in $this->get('markdown.parser'):

... lines 1 - 13
class GenusController extends Controller
{
... lines 16 - 58
public function showAction($genusName)
{
... lines 61 - 69
$markdownTransformer = new MarkdownTransformer(
$this->get('markdown.parser')
);
... lines 73 - 99
}
... lines 101 - 125
}

Try it out!

It's alive! Twig is escaping the <p> tag - but that proves that markdown parsing is happening. The process we just did is dependency injection. It basically says: if an object needs something, you should pass it to that object. It's really programming 101. But if it still feels weird, you'll see a lot more of it.

Leave a comment!

8
Login or Register to join the conversation
Default user avatar
Default user avatar 3amprogrammer | posted 5 years ago | edited

I have found another cool trick! I see that you render view like this:


return $this->render('genus/show.html.twig', array(
    'genus' => $genus,
    'funFact' => $funFact,
    'recentNoteCount' => count($recentNotes)
));

That's okay, but If you change the string indicating the view name you will get autocompletion in twig!


return $this->render(':genus:show.html.twig', array(
    'genus' => $genus,
    'funFact' => $funFact,
    'recentNoteCount' => count($recentNotes)
));
Reply

Hey 3amprogrammer!

You should get auto-completion in Twig in both cases. Well, actually, the auto-completion seems to be inconsistent in Twig - I only use the simple template name (e.g. genus/show.html.twig) and I get auto-completion *most* of the time, but not always. I'm not sure why it works sometimes, but not other times - I would love to know why! :)

Cheers!

1 Reply
mehdi Avatar

the "funFact" value does not contain the < p > tag. where does this tag come from?

Reply

Hey mehdi!

That's markdown! Well, at least, the markdown parser that we're using wraps all the text in a &lt;p&gt; tag :).

Cheers!

1 Reply
Default user avatar
Default user avatar Muhammad Taqi Hassan | posted 5 years ago

Hi, when i run php app/console container:debug, most of the service id has class Names infront of them, but some of them has not, i.e service_container and request . Why this?

Reply

Hey Muhammad!

Yea, really good question! First, when a bundle adds a service to the system, it can choose to call it whatever it wants - there are no rules about what a service can be called. So, there is no technical reason for these different names. What you are seeing is am mix between some standards and some "practicality". Basically:

1) If you're creating a re-usable/shareable bundle (this includes the core bundles), it's typical a best practice to somewhat "namespace" your service ids so they don't collide with others. In other words, if I made a KnpCoolGuyBundle, then I might prefix my service ids with knp_coolguy so that they don't collide with service ids from other bundles. This is what you're seeing: service id's are basically being prefixed with a string that's similar to their namespace. But this is just a standard, there's no technical reason for this.

2) On the other hand, nobody likes long names, especially for really commonly-used things. It would be a bummer to have a service called symfony_component_dependency_injection_service_container :). So, sometimes, a bundle will be opinionated and create a service with a very short name for convenience. This is why you have services like doctrine, templating or logger.

I hope that explains it a bit! Cheers!

Reply
Default user avatar
Default user avatar guillaume | posted 5 years ago

You are a great teacher honestly !!! I am very happy to follow all your courses :)

Reply

Cheers and thanks! Very happy they are useful!! :)

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
    }
}
userVoice