Buy
Buy

Autowiring & Service Deprecations

Woo! There are only two deprecations left on the homepage... but they're weird! And actually, they're not real! These are false deprecation warnings!

Upgrade to the Symfony 3.3 services.yml Config!

In our Symfony 3.3 Tutorial, we talked a lot about all the new service autowiring & auto-registration stuff. We also upgraded our old services.yml file to use the new fancy config. It turns out that doing this is one of the biggest steps in upgrading to Symfony 4 and Flex. If you have not already upgraded your services.yml file to use autowiring & service auto-registration, stop and go through the Symfony 3.3 tutorial now.

Strict Autowiring Mode

The way that autowiring works changed in Symfony 4. In Symfony 3, when Symfony saw a type-hint - like EntityManager - it would first look to see if there was a service or alias in the container with that exact id: so Doctrine\ORM\EntityManager. If there was not, it would then scan every service looking to see if any were an instance of this class. That magic is gone.

In Symfony 4, it's simpler: autowiring only does the first part: if there is not a service whose id is Doctrine\ORM\EntityManager, it throws an exception. This is a great change: the system is simpler and more predictable.

So, of course, if any of your autowiring depends on the old, deprecated logic, you'll see a deprecation message. And yea, that's where these messages are coming from!

But, there's a better way to find this deprecated logic. Open app/config/config.yml. Under parameters, add container.autowiring.strict_mode: true.

... lines 1 - 7
parameters:
... lines 9 - 10
container.autowiring.strict_mode: true
... lines 12 - 81

This tells Symfony to use the simpler, Symfony 4-style autowiring logic right now. Instead of deprecations, you'll see great big, beautiful errors when you try to refresh.

So... try it! Refresh the homepage. Woh! No errors! That's because we already fixed all our old autowiring logic in the Symfony 3.3 tutorial. And... the 2 deprecation messages are gone! Those were not real issues: it's a rare situation where the deprecation system is misreporting.

Services are now Private

So yes! This means that... drumroll... our homepage is ready for Symfony 4.0! But the rest of the site might not be. Surf around to see what other deprecations we can find. The login page looks ok: login with weaverryan+1@gmail.com, password iliketurtles. Go to /genus... still no issues and then... ah! Finally, 1 deprecation on the genus show page.

Check it out. Interesting:

The "logger" service is private, getting it from the container is deprecated since Symfony 3.2. You should either make this service public or stop using the container directly.

Wow! This is coming from GenusController line 84. Go find it! Close a few files, then open this one. Scroll down to 84. Ah!

This is really important. Open services.yml. These days, all of our services are private by default: public: false. This allows Symfony to optimize the container and all it really means is that we cannot fetch these services directly from the container. So $container->get() will not work.

In Symfony 4, this is even more important because a lot of previously public services, like logger, are now private. Here's the point: you need to stop fetching services directly form the container... everywhere. It's just not needed anymore.

Fixing $container->get()

What's the solution? Since we're in a controller, add a LoggerInterface $logger argument. Then, just $logger->info().

... lines 1 - 15
class GenusController extends Controller
{
... lines 18 - 78
public function showAction(Genus $genus, MarkdownTransformer $markdownTransformer, LoggerInterface $logger)
{
... lines 81 - 84
$logger->info('Showing genus: '.$genus->getName());
... lines 86 - 94
}
... lines 96 - 140
}

Isn't that better anyways? As soon as we refresh the page... deprecation gone!

Where else are we using $container->get()? Let's find out! In your terminal, run:

git grep '\->get('

Ah, just two more! We may not need to change all of these... some services are still public. But let's clean it all up!

Start in SecurityController. Ah, here it is. So: what type-hint should we use to replace this? Well, you could just guess! Honestly, that works a lot. Or try the brand new console command:

./bin/console debug:autowiring

Sweet! This is a full list of all type-hints you can use for autowiring. Search for "authentication" and... there it is! This type-hint is an alias to the service we want.

That means, back in SecurityController, delete this line and add a new AuthenticationUtils $authenticationUtils argument. Done.

... lines 1 - 9
class SecurityController extends Controller
{
... lines 12 - 14
public function loginAction(AuthenticationUtils $authenticationUtils)
{
... lines 17 - 33
}
... lines 35 - 42
}

The last spot is in UserController: we're using security.authentication.guard_handler. This time, let's guess the type-hint! Add a new argument: Guard... GuardAuthenticationHandler. That's probably it! And if we're wrong, Symfony will tell us. Use that value below.

... lines 1 - 13
class UserController extends Controller
{
... lines 16 - 18
public function registerAction(Request $request, LoginFormAuthenticator $authenticator, GuardAuthenticatorHandler $guardHandler)
{
... lines 21 - 32
return $guardHandler
... lines 34 - 39
}
... lines 41 - 44
}
... lines 46 - 81
}

And yep, you can see the GuardAuthenticationHandler class in the debug:autowiring list. But... what if it weren't there? What if we were trying to autowire a service that was not in this list?

Well... you would get a huge error. And maybe you should ask yourself: is this not a service I'm supposed to be using?

But anyways, if it's not in the list, there's a simple solution: go to services.yml and add your own alias. At the bottom, paste the class you want to use as the type-hint, then copy the service id, and say @ and paste.

... lines 1 - 5
services:
... lines 7 - 50
# example of adding aliases, if one does not exist
# Symfony\Component\Security\Guard\GuardAuthenticatorHandler: '@security.authentication.guard_handler'

Yep, that is all you need to do in order to define your own autowiring rules. Since we don't need it in this case, comment it out.

Ok, refresh! At this point, our goal is to hunt for deprecations until we're pretty confident they're gone: it's not an exact science. If you have a test suite, you can use the symfony/phpunit-bridge to get a report of deprecated code paths that are hit in your tests.

Adding $form->isSubmitted()

There is one more deprecation on the registration page. Look at the details:

Call Form::isValid() on an unsubmitted form is deprecated. Use Form::isSubmitted() before Form::isValid().

This comes from UserController. Open that class and search for isValid(). Before $form->isValid(), add $form->isSubmitted(). Find again and fix the other spot. This isn't very important... you just need both in Symfony 4.

... lines 1 - 13
class UserController extends Controller
{
... lines 16 - 18
public function registerAction(Request $request, LoginFormAuthenticator $authenticator, GuardAuthenticatorHandler $guardHandler)
{
... lines 21 - 23
if ($form->isSubmitted() && $form->isValid()) {
... lines 25 - 44
}
... lines 46 - 59
public function editAction(User $user, Request $request)
{
... lines 62 - 64
if ($form->isSubmitted() && $form->isValid()) {
... lines 66 - 80
}
}

And now... I think we're done! All the deprecations I could find are gone.

It's time to upgrade to Symfony 4. Which, by the way, is the fastest Symfony version ever! Zoom!

Leave a comment!

  • 2018-08-01 Victor Bocharsky

    Hey the_nuts ,

    You're right, if you do not use doctrine/common directly in your project - just upgrade deps in the future and this error will be gone. Thanks for sharing the link btw!

    Cheers!

  • 2018-08-01 Victor Bocharsky

    Hey Abdelamine,

    Do you use the Doctrine\Common\ClassLoader somewhere in your project? Probably not, I think it's just used somewhere internally. If so - you don't need to do any actions, you just need to wait for updates and upgrade your dependencies. But if you use doctrine/common - see the solution in the stackoverflow post linked by the_nuts.

    Cheers!

  • 2018-07-31 Abdelamine Mehdaoui

    thanks the_nuts,
    I've already seen this link but I did not understand how and where repalce the three pakages(version 3.4), but in the tutoriel ts removed before having the error (to get version 4.0) , so my question is it possible to replace it when we update to 4.0

  • 2018-07-31 the_nuts

    +1. Apparently we have to wait for a Symfony update https://stackoverflow.com/q...

  • 2018-07-31 Abdelamine Mehdaoui

    Hey ryan/victor/diego
    what about this deprecaton "User Deprecated: Doctrine\Common\ClassLoader is deprecated." can you explain me what I will do to delete it?
    thank you

  • 2018-04-05 Victor Bocharsky

    Hey Vlad,

    1. Hm, if you do not want to inject services into actions - inject them into the constructor instead, i.e. just typehint LoggerInterface in the __construct() method of your controller and logger service will be injected into the entire controller.

    2. The same answer, inject "jms_serializer"'s class into the constructor of your controller, then you'll be able to call it even in private methods.

    Cheers!

  • 2018-04-04 Dan Meigs

    Thanks, Ryan!

    I went back to Symfony 3.3 and looked at the deprecation messages, and there it was:

    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 "Doctrine\ORM\EntityManager" in "AppBundle\Controller\Teams\TeamsController" to one of its parents: interface "Doctrine\ORM\EntityManagerInterface", or interface "Doctrine\Common\Persistence\ObjectManager".

    Now that's a beautiful error message!

  • 2018-04-04 weaverryan

    Hey Dan Meigs!

    Great thinking to turn on container.autowiring.strict.mode. So yes, let's demystify what's going on here! It's a few things all at once:

    1) Instead of EntityManager $em, use EntityManagerInterface. Each bundle decides which type-hints it wants to "support" for autowiring (in this case, it's DoctrineBundle), and the best-practice is to support interfaces, but not concrete classes (there are exceptions of course, including times when a service has no interface). The idea is to gently "nudge" you into typing against interfaces, not classes. This should fix everything :).

    2) The second part - the big beautiful error - well... that's a different issue ;). What I mean is... this error may be big, but it ain't beautiful! When autowiring fails, you normally get a very clear error - something like "Cannot autowire argument $em. Please try using the type-hint EntityManagerInterface" instead. BUT, when autowiring fails for controller *arguments*, you do *not* see this nice message! This was a bug that we couldn't fix in time for Symfony 3.4 & 4.0. But, it HAS been fixed in Symfony 4.1. So, watch out for this - but in the future, you would have gotten a more clear error.

    Cheers!

  • 2018-04-03 Vlad

    Hello Ryan,
    I have a couple of questions regarding autowiring:

    1. how would you autowire an overridden setContainer() method that's part of ContainerAwareInterface? For example, I get the logger from the container there and use it throughout the class, instead of getting it in each action.

    2. how do you autowire in methods that aren't actions? For example, in your REST API tutorials, you use methods serialize() and deserialize() in which you call $this->container->get('jms_serializer').

    Thank you!

  • 2018-04-02 Dan Meigs

    When upgraded to 3.3 and made all my autowiring changes, I decided that having
    $em = $this->getDoctrine()->getManager();
    at the start of dozens of functions was tedious and, I thought, could be replaced by dependency injection like so:
    public function foo(EntityManager $em, Request $request)

    That worked great...until I upgraded to 3.4 and turned on container.autowiring.strict.mode. Now I get a big beautiful error message:
    Controller "AppBundle\Controller\Tournaments\TournamentController::tournamentAction()" requires that you provide a value for the "$em" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.

    I know I could fix this by getting the entity manager from Doctrine with $em = $this->getDoctrine()->getManager(), but I don't really understand why it's a problem in the first place. Can you explain what's going on?

  • 2018-01-22 Diego Aguiar

    Hey the_nuts

    Great question! I think you are right, but the performance boost would be for a few micro-seconds or less (depending on which processor you have). If you are on Symfony4 the Container comes very optimized (so you can autowire easily), and by default all your services are not public, so that's why is better to adopt the habit of injecting dependencies as arguments in your controller's action

    If you have an use case where injecting a service is causing a bottle neck, then you may want to code another endpoint, so, that endpoint will always use what it requires.

    Cheers!

  • 2018-01-22 the_nuts

    Another question about autowiring: if I need a service only sometimes (if(some_condition) { $this->addFlash($this->get('translator')->...) } isn't it less efficient to always inject the translator with autowiring? Shouldn't I load it only when needed?