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

Service Subscriber: Lazy Performance

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 $12.00

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

Login Subscribe

Our nice little Twig extension has a not-so-nice problem! And... it's subtle.

Normally, if you have a service like MarkdownHelper:

... lines 1 - 4
use App\Service\MarkdownHelper;
... lines 6 - 9
class AppExtension extends AbstractExtension
{
private $markdownHelper;
public function __construct(MarkdownHelper $markdownHelper)
{
$this->markdownHelper = $markdownHelper;
}
... lines 18 - 29
}

Symfony's container does not instantiate this service until and unless you actually use it during a request. For example, if we try to use MarkdownHelper in a controller, the container will, of course, instantiate MarkdownHelper and pass it to us.

But, in a different controller, if we don't use it, then that object will never be instantiated. And... that's perfect! Instantiating objects that we don't need would be a performance killer!

Twig Extensions: Always Instantiated

Well... Twig extensions are a special situation. If you go to a page that renders any Twig template, then the AppExtension will always be instantiated, even if we don't use any of its custom functions or filters. Twig needs to instantiate the extension so that it knows about those custom things.

But, in order to instantiate AppExtension, Symfony's container first needs to instantiate MarkdownHelper. So, for example, the homepage does not render anything through markdown. But because our AppExtension is instantiated, MarkdownHelper is also instantiated.

In other words, we are now instantiating an extra object - MarkdownHelper - on every request that uses Twig... even if we never actually use it! It sounds subtle, but as your Twig extension grows, this can become a real problem.

Creating a Service Subscriber

We somehow want to tell Symfony to pass us the MarkdownHelper, but not actually instantiate it until, and unless, we need it. That's totally possible.

But, it's a little bit tricky until you see the whole thing put together. So, watch closely.

First, make your class implement a new interface: ServiceSubscriberInterface:

... lines 1 - 6
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
... lines 8 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
... lines 14 - 40
}

This will force us to have one new method. At the bottom of the class, I'll go to the "Code"->"Generate" menu - or Command+N on a Mac - and implement getSubscribedServices(). Return an array from this... but leave it empty for now:

... lines 1 - 6
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
... lines 8 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
... lines 14 - 34
public static function getSubscribedServices()
{
return [
... line 38
];
}
}

Next, up on your constructor, remove the first argument and replace it with ContainerInterface - the one from Psr - $container:

... lines 1 - 5
use Psr\Container\ContainerInterface;
... lines 7 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
... lines 14 - 15
public function __construct(ContainerInterface $container)
{
... line 18
}
... lines 20 - 40
}

Also rename the property to $container:

... lines 1 - 5
use Psr\Container\ContainerInterface;
... lines 7 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
... lines 20 - 40
}

Populating the Container

At this point... if you're totally confused... no worries! Here's the deal: when you make a service implements ServiceSubscriberInterface, Symfony will suddenly try to pass a service container to your constructor. It does this by looking for an argument that's type-hinted with ContainerInterface. So, you can still have other arguments, as long as one has this type-hint.

But, one important thing: this $container is not Symfony's big service container that holds hundreds of services. Nope, this is a mini-container, that holds a subset of those services. In fact, right now, it holds zero.

To tell Symfony which services you want in your mini-container, use getSubscribedServices(). Let's return the one service we need: MarkdownHelper::class:

... lines 1 - 4
use App\Service\MarkdownHelper;
... lines 6 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
... lines 14 - 34
public static function getSubscribedServices()
{
return [
MarkdownHelper::class,
];
}
}

When we do this, Symfony will basically autowire that service into the mini container, and make it public so that we can fetch it directly. In other words, down in processMarkdown(), we can use it with $this->container->get(MarkdownHelper::class) and then ->parse($value):

... lines 1 - 4
use App\Service\MarkdownHelper;
... lines 6 - 11
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
... lines 14 - 27
public function processMarkdown($value)
{
return $this->container
->get(MarkdownHelper::class)
->parse($value);
}
... lines 34 - 40
}

At this point, this might feel like just a more complex version of dependency injection. And yea... it kinda is! Instead of passing us the MarkdownHelper directly, Symfony is passing us a container that holds the MarkdownHelper. But, the key difference is that, thanks to this trick, the MarkdownHelper service is not instantiated until and unless we fetch it out of this container.

Understanding getSubscribedServices()

Oh, and to hopefully make things a bit more clear, you can actually return a key-value pair from getSubscribedServices(), like 'foo' => MarkdownHelper::class:

class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
    // ...
    public static function getSubscribedServices()
    {
        return [
            'foo' => MarkdownHelper::class,
        ];
    }
}

If we did this, it would still mean that the MarkdownHelper service is autowired into the mini-container, but we would reference it internally with the id foo.

If you just pass MarkdownHelper::class as the value, then that's also used as the key.

The end result is exactly the same as before, except MarkdownHelper is lazy! To prove it, put a die statement at the top of the MarkdownHelper constructor.

Now, go back to the article page and refresh. Not surprising: it hits the die statement when rendering the Twig template. But now, go back to the homepage. Yes! The whole page prints: MarkdownHelper is never instantiated.

Go back and remove that die statement.

Tip

There is a better way for creating lazy-Loaded Twig extensions since Twig v1.26. First of all, create a separate class, e.g. AppRuntime, that implements RuntimeExtensionInterface and inject MarkdownHelper object there. Also, move processMarkdown() method there:

namespace App\Twig;

use App\Service\MarkdownHelper;
use Twig\Extension\RuntimeExtensionInterface;

class AppRuntime implements RuntimeExtensionInterface
{
    private $markdownHelper;

    public function __construct(MarkdownHelper $markdownHelper)
    {
        $this->markdownHelper = $markdownHelper;
    }

    public function processMarkdown($value)
    {
        return $this->markdownHelper->parse($value);
    }
}

And then, in AppExtension, remove MarkdownHelper at all and point the cached_markdown filter to [AppRuntime::class, 'processMarkdown'] instead:

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class AppExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('cached_markdown', [AppRuntime::class, 'processMarkdown'], ['is_safe' => ['html']]),
        ];
    }
}

That's it! Now our Twig extension does not have any direct dependencies, and AppRuntime object will be created only when cached_markdown is called.

Here's the super-duper-important takeaway: I want you to use normal dependency injection everywhere - just pass each service you need through the constructor, without all this fancy service-subscriber stuff.

But then, in just a couple of places in Symfony, the main ones being Twig extensions, event subscribers and security voters - a few topics we'll talk about in the future - you should consider using a service subscriber instead to avoid a performance hit.

Leave a comment!

45
Login or Register to join the conversation
Francisc Avatar
Francisc Avatar Francisc | posted 3 years ago | edited

so there, I am using the ServiceSubscriberInterface to inject into my Command the Psr Container. Then I want to enable some services in the Command, namely all the messenger transport I have defined: "transport_high", "transport_normal". I do this because I want to call the getMessageCount() on each transport. BUT, the problem is that I get this error: The service "App\Command\WorkCommand" has a dependency on a non-existent service "Symfony\Component\Messenger\Transport\TransportInterface". What service is the transport service then? And how can I access all of the transports? Thank you!

1 Reply
Francisc Avatar
Francisc Avatar Francisc | Francisc | posted 3 years ago | edited

so this is what I've done intuitively

services.yaml


services:
        Mars\Command\DoCloudCommand:
            tags:
                - { name: 'container.service_subscriber', key: 'cloud', id: 'messenger.transport.cloud' }

Command class:

public static function getSubscribedServices()
{
    return [
        'cloud' => ServiceLocatorArgument::class
    ];
}

and it works.

Can you shed more light on it and maybe direct me to some documentation covering this edge case please?

Reply

Hey Francisc !

Excellent question - and nice job finding the solution! So, there are 2 separate things going on... and I don't want to confuse them.

1) Pretend for a moment that we're not using a service subscriber, because the first "thing going on" has nothing to do with service subscribers. In Symfony's container, there are many services. It depends on your app, but let's pretend that there are 200 services. The majority of these services are not "meant" for you to use directly. I mean, you can use them directly, but Symfony makes it "not very easy". Specifically, if Symfony "wants" you to use a service, it configures an "autowiring alias" for it. For example, Symfony DOES want you to use the logger service directly. So it creates an alias from Psr\Log\LoggerInterface to logger so that you can type-hint Logger and get that service. But Symfony doesn't do this for all services in the container. If you have 200 services in the container, probably only about 50 of them have "autowiring aliases". You can use those other 150 services if you want to, but you need to do more work. Specifically, you need to manually "wire" them into a service. Usually, that would look like this:


services:
    My\Cool\Service:
        bind:
            $someArgumentName: '@some_service_id'

In this case, I'm pretending that this some_service_id service is a service that I want to access, but it does not have an autowiring alias. So, I configure it specifically on the service: Symfony will know to pass this service to my $someArgumentName argument (you could also use arguments instead of bind - they are equivalent in most cases.

2) The above description is the really important piece to all of this. Now let's talk about service subscribers :). By default, they work via autowiring: your getSubscribedServices method returns an array of autowiring type-hints that you want to be included in your service locator. If you use a interface/class that doesn't have an autowireable alias, it won't work. That's what's happening with the transports. It's not that common to need to interact with transport service directly. And so, autowiring alias are not made available. This is why it didn't work originally. Your solution above (which is correct) is how you explicitly configure a service inside a service locator when it doesn't have an autowireable alias.

Phew! I hope that helps! The big picture is that you're trying to reference services that are not autowireable. You could also make them autowireable with global binds:


services:
    _defaults:
        # ...
        bind:
            # now you can autowire the cloud transport by using the TransportInterface type-hint AND naming the argument cloudTransport:
            # public function __construct(TransportInterface $cloudTransport)
            Symfony\Component\Messenger\Transport\TransportInterface $cloudTransport: '@messenger.transport.cloud'

Let me know if that demystifies it! Fun stuff!

Cheers!

Reply
Anthony R. Avatar
Anthony R. Avatar Anthony R. | posted 4 years ago

Hello,
Is this solution of lazy loading twig extensions a new way to do it ?
Thanks !

1 Reply
Beis Avatar

Hy Guys, which is the diference between Psr\Container\ContainerInterface and Symfony\Component\DependencyInjection\ContainerInterface ? Thanks!

1 Reply
sadikoff Avatar sadikoff | SFCASTS | Beis | posted 4 years ago | edited

Hey Beis

It's quite simple. Psr\Container\ContainerInterface is more low level Interface which describes basic PSR Container, however Symfony\Component\DependencyInjection\ContainerInterface is more complex and of course it extends Psr\Container\ContainerInterface. If you not sure which one to use for autowire you could choose one from Psr\. In Symfony both interfaces are aliases to server_container and you will get the same object, only IDE autocompletion will be different a little.

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | posted 4 years ago | edited

Hello there ,

Thanks for this tutorial I didn't see it until you linked it from another video, I'm going to apply it right now. But now I have for me one of my big doubts, I'm going to have a hard time explaining it because I don't know how to name it, I wouldn't even know how to search my doubt in google, so I'm going to try to explain myself as best as possible:

Right now I'm finishing a CMS with Symfony which does not stop refactoring(I always learn new things).

I have several services:

Theme
Nav
Shortcode

Very similar to Wordpress, I really like Wordpress.

But now I want to make that for example

create an interface of each service or class

and if for example I want to create a new Theme class that implements that interface to have it in any place of my application, the same for Shortcode and Nav. Actually the system I want to understand or do is very similar to Wordpress, for example in Wordpress in the functions.php file if you create and apply the add_shortcode function will be available anywhere. So my big doubt is that I have a chance to do it as organized and automatic as possible with Symfony.

Thanks!!!

UPDATE:

I just saw this post https://symfony.com/blog/ne... , which looks like it could do something similar to what I want, but I don't like the idea of every class I make or implement of for example my Interface Theme or Nav, I have to add it to services.yml, I don't think it's automated.

Any more ideas?

1 Reply

Hey Jose carlos C.

I'm not sure I'm following what you want to achieve. What you mean with "create an interface of each service or class". Have you read about compiler pass?

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | MolloKhan | posted 4 years ago

Hey, Yes I think that's the solution I'm gonna investigate what's going on.

Thanks

Reply
Jose carlos C. Avatar

Finally I found it here https://symfonycasts.com/sc..., of course I didn't know the concept and I didn't know how to search. I'm going to try if I have any questions in linked video.

Thanks so much

Reply

Good find Jose carlos C.!

The tagging / compiler pass system is super advanced (not necessarily super hard, just not *that* commonly needed) but super power and cool. There is also a shortcut syntax if you want to automatically "inject" all classes with a certain "tag" into one of your services - https://symfony.com/blog/ne...

If you combine that with the "autoconfigure" feature, then you could make it so that ever class with a certain interface automatically gets passed to one of your services. Totally doable - the Symfony core does it all the time. Let us know if you have questions (maybe you already have and I haven't checked yet!).

Cheers!

1 Reply
jayson222 Avatar
jayson222 Avatar jayson222 | posted 3 years ago | edited

Hello there!
If you want to test that your MardownHelper instantiates when calls cached_markdown filter but you are using the updated version of Lazy-Loaded Twig Extensions then put die; into constructor of AppRuntime service!


class AppRuntime implements RuntimeExtensionInterface
{
	private $markdownHelper;

	public function __construct(MarkdownHelper $markdownHelper)
	{
		die;
		$this->markdownHelper = $markdownHelper;
	}

	public function processMarkdown($value)
	{
		return $this->markdownHelper->parse($value);
	}
}

Enjoy :)

Reply

Hey Jayson,

Thank you for this tip ;) More information about it you can find in Symfony docs: https://symfony.com/doc/cur...

Cheers!

1 Reply
Ryan C. Avatar
Ryan C. Avatar Ryan C. | posted 4 years ago

@2:30 ALT+Insert for Windows :)

Reply

Hey Ryan,

Thanks for sharing it with others Windows users ;)

Cheers!

Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 4 years ago

is it worth the extra code? How much performance gain can we get from this trick? I though instatnces of services are very small things.

Reply

Hey Lijana Z.

That's a good question. It depends, if you have a dependency of a service which opens a DB connection or communicates to a email server, then, for sure you will want it to be lazy. I always do this trick, it's quite fast and simple to implement :)

Cheers!

Reply
Lijana Z. Avatar

yea, maybe on those cases, but I do not know, if I use dependencies which open DB connection, unless of those dependency depencies do this. Connectioin should be opened in constructor as I understand for this to be a problem but I do not do this.

Are there some symfony classes openening DB connections on instantiation which I should think about?

Reply

I believe an Entity Repository opens up a DB connection upon instantiation but I'm not totally sure

Reply

It's not works! I did all as shown. And got error:
Uncaught PHP Exception Twig_Error_Runtime: "An exception has been thrown during the rendering of a template ("The "App\Service\MarkdownHelper" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.")." at /Users/eugem/Developer/PHP/php-2HW08.mac/templates/article/show.html.twig line 31
Help me please

Reply
Beis Avatar

Any update on that? It is happening to me. It is happening to me when die on the MarkdownHelper constructor. This helper is used as a filter. On the video, error does not appears, and title is shown...

Reply
Beis Avatar
Beis Avatar Beis | Beis | posted 4 years ago | edited

I saw a question below. I was using wrong ContainerInterface. That's the correct one:
use Psr\Container\ContainerInterface;

1 Reply
sadikoff Avatar sadikoff | SFCASTS | Beis | posted 4 years ago | edited

hey Beis

Awesome that you found what's wrong! If you will have some issues in feature stay in touch!

Cheers!

Reply

Hey Eugem

Check where you are fetching the service `MarkdownHelper` from the container and instead try using Dependency Injection or implement a ServiceContainer

Cheers!

Reply

Hello,
first of all, your website and its content is just mindblowing! almost to good to be true. thank you for that!

So my question is kinda trivial. Why is MarkdownHelper.php inside the Service-folder and not in the Helper-folder? Classes inside Helper should be an autowired Service aswell right?

Thanks in advance and keep on pushin'!

Reply

Hey AndTheGodsMadeLove

Thank you for your king words :)

About your question. You are correct, almost everything inside the "src/" directory is autowired and autoconfigured (if you didn't change your "services.yaml" file). The MarkdownHelper class lives inside the service directory only for highlighting the concept of services, but yes, it would fit better inside "Helper" directory

Cheers!

Reply
Dominik Avatar
Dominik Avatar Dominik | posted 4 years ago

Sorry, it's me again. I can't understand the part of that video when we implement "getSubscribedServices" method from interface. I don't see point when we call that implemented method.

Second question. In constructor we type hint interface "ContainerInterface" not a class. In my knowledge (still not good enough) we use interfaces to defined head of methods and during interface implementation we have to define body of that implemented methods. So how come we can type hint interface and use not implemented method? Did I miss something from previous tutorials?

Regards

Reply

Hey Dominik!

Sorry, it's me again.

Ha! It's no problem of course :)

I can't understand the part of that video when we implement "getSubscribedServices" method from interface. I don't see point when we call that implemented method.

Excellent question. And you're right: we NEVER call this method. Instead, when Symfony is building its container, it "notices" that we implement this interface and IT calls this method. It uses that information to create a "mini container" for us that it passes as the first argument to the method. This method basically provides configuration that is used by Symfony's core.

In constructor we type hint interface "ContainerInterface" not a class. In my knowledge (still not good enough) we use interfaces to defined head of methods and during interface implementation we have to define body of that implemented methods. So how come we can type hint interface and use not implemented method? Did I miss something from previous tutorials?

Another good question :). If you dump($container) in the constructor, you will find that the object has a Symfony\Component\DependencyInjection\ServiceLocator class - which is a normal, concrete class (not an interface). So, you're 100% correct that you will always work with concrete classes. But, this ServiceLocator class DOES implement this ContainerInterface. So then, if we are passed a ServiceLocator object, you might wonder: why not use the ServiceLocator class in the type-hint for the argument instead of ContainerInterface? The answer is that, in general, using interfaces for your type-hints is a "better practice" that concrete classes. Even if you never need to do it, by using the interface type-hint, you (or Symfony) could, in the future, replace the ServiceLocator class with a totally different class. As long as that new class implements ContainerInterface, your code will still work. By using interface type-hints, we allow the "implementation" (i.e. specific class) to be replaceable with any other implementation of that interface.

In fact, Symfony feels so strongly about promoting this best practice, the a lot of "autowiring" (the ability to type-hint an argument and have Symfony automatically pass you the correct service) ONLY works if you use the interface. For example, in this case, if you change the type-hint to ServiceLocator, it will not work - Symfony is more or less "forcing" you to use the interface to promote that best practice.

Let me know if this helps!

Cheers!

1 Reply
Dominik Avatar

Now everything got sense. Thank you for brilliant and clear answer :). I regret now I didn't find this website when I started learning PHP :).

Regards.

Reply
Abelardo Avatar
Abelardo Avatar Abelardo | posted 4 years ago

Hi there!

I wrote "die;" at the top of the constructor and then I went to the article page. It resulted in a blank page (expected result); so, when I went to the 127.0.0.1:8000, it remains the blank page. What's wrong?

Cheers!

Reply

Hey Abelardo L.

I believe you are rendering an Article in your front page and because of that "die" statement your script stops being executed. Just remove the "die" statement :)

Cheers!

Reply
Default user avatar

Same for me, writing die in the constructor results empty page on the homepage.

Reply

Hey Wazir Khan

Is there any other place where you are using the MarkdownHelper service?

Cheers!

Reply
Default user avatar
Default user avatar Wazir Khan | MolloKhan | posted 4 years ago | edited

Nope, checked the code again and again, earlier it was used in the show controller, removed that including the use statement from the ArticleController class. But still same result. The difference that I can see (comparing tutorial code and my code) is that I am using SF4.2.8 and the Interface used in the code is deprecated now. But it comes in another namespace Symfony\Contracts\Service\ServiceSubscriberInterface, though, I tried with the deprecated interface as well, didn't work that too. my bad luck :)

Reply

Hmm, that's weird. I believe I'll have to look at your code. Can you upload it to github or somewhere else?

1 Reply
Default user avatar
Default user avatar Wazir Khan | MolloKhan | posted 4 years ago | edited

Hi MolloKhan , thank you for the prompt support. Just pushed my code to github.
Here's the link: https://github.com/ahmadzai...

Reply

Ohh, so the die statement is inside the Twig extension class. That class is not being lazy loaded, what's been lazy loaded are all the services you define inside public static function getSubscribedServices(), in other words, MarkdownHelper

1 Reply
Default user avatar

Thank you so much, I should have re-read/re-listened the scripts. Sorry for inconvenience.
I got the point. Working with Sf for some time, but on crazy way, now learning many new things that I never heard about. Once again thanks for such a fun tutorials (Y)

Reply

NP man! I'm glad to hear that you like our tutorials :)

Cheers!

Reply
Qcho Avatar

I think a more elegant solution instead of following the service subscriber pattern is to use (as commented) the Lazy Service proxy:
https://symfony.com/doc/cur... and use standard Dependency Injection.

You don't need to follow my advice, who am I right?, but I think no one will discourage this:
https://www.php-fig.org/psr...

1.3 Recommended usage

Users SHOULD NOT pass a container into an object so that the object can retrieve its own dependencies.

This means the container is used as a Service Locator

which is a pattern that is generally discouraged.

Please refer to section 4 of the META document for more details.

Reply

Hey Qcho!

You make a great point! We are absolutely using a service locator pattern in this situation. In fact, the whole "service locator"/"service subscriber" feature we're using in Symfony was made possible by PSR-11 (the fact that the Container->get() method has a standard interface).

In general, yea, I think using a lazy service proxy is a simpler solution - you just use DI like every other places, so there's nothing extra to learn. But, there's one problem: you can currently only make your *own* services lazy - not core services. We could totally make MarkdownHelper lazy here. But, if we also needed the entity manager, we cannot make that lazy (well, technically we could with a compiler pass, but gets really complex). The service locator is a way around this. It's not as elegant as normal DI for sure. But, at least we're not passing in the *entire* container: we're passing in a "mini" container that only has the stuff we need in it. It's an unfortunate, necessary evil in some situations.

Cheers!

Reply

Does exist similar solution for symfony 2.8 ?

Reply

Hey bartek

Are you talking about the "autoconfigure" functionality? In that case, I'm fraid not. You have to define your Twig extension as a service and give it its tag

Cheers!

Reply

I meant Lazy Performance solution in twig.

Reply

Oh, in that case you can just fetch it from the container, that's how things was done in Symfony2

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "fzaninotto/faker": "^1.7", // v1.7.1
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.4.0
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice