Is your Container Running? Catch It! lint: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 $10.00

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

Login Subscribe

Symfony's service container is special... like super-powers special. Why? Because it's "compiled". That's a fancy way of saying that, instead of Symfony figuring out how to instantiate each service at runtime, when you build your cache, it figures out every argument to every service and dumps that info into a cache file - called the "compiled container". That's a major reason why Symfony is so fast.

But it has another benefit: if you misconfigured a service - like used a wrong class name - you don't have to go to a page that uses that service to notice the problem. Nope, every page of your app will be broken. That means less surprise bugs on production.

Another type of error that Symfony's container will catch immediately is a missing argument. For example, imagine you registered a service and forgot to configure an argument. Or, better example, Symfony couldn't figure out what to pass to this Mailer argument for some reason:

... lines 1 - 12
class Mailer
{
... lines 15 - 19
public function __construct(MailerInterface $mailer, Environment $twig, Pdf $pdf, EntrypointLookupInterface $entrypointLookup)
{
... lines 22 - 25
}
... lines 27 - 65
}

If that happened, you'll get an error when the container builds... meaning that every page will be broken - even if a page doesn't use this service.

Detecting Type Problems

Starting in Symfony 4.4, Symfony can now also detect if the wrong type will be passed for an argument. For example, if we type-hint an argument with MailerInterface, but due to some misconfiguration, some other object - or maybe a string or an integer - will be passed here, we can find out immediately. But this type of problem won't break the container build. Instead, you need to ask Symfony to check for type problems by running:

php bin/console lint:container

And... oh! Woh! This is a perfect example!

Invalid definition for service nexy_slack.client. Argument 1 of Nexy\Slack\Client accepts a Psr ClientInterface, HttpMethodsClient passed.

Apparently the container is configured to pass the wrong type of object to this service! This service comes from NexylanSlackBundle - I broke something when I upgraded that bundle... and didn't even realize it because I haven't navigated to a page that uses that service!

Fixing our lint Problem

After some digging, it turns out that the bundle has a tiny bug that allowed us to accidentally use a version of a dependency that is too old. Run:

composer why php-http/httplug

I won't bore you with the details, but basically the problem is that this library needs to be at version 2 to make the bundle happy. We have version 1 and a few other libraries depend on it.

The fix is to go to composer.json and change the guzzle6-adapter to version 2:

105 lines composer.json
{
... lines 2 - 3
"require": {
... lines 5 - 22
"php-http/guzzle6-adapter": "^2.0",
... lines 24 - 44
},
... lines 46 - 103
}

Why? Again, if you dug into this, you'd find that we need version 2 of guzzle6-adapter in order to be compatible with version 2 of httplug... which is needed to be compatible with the bundle. Sheesh.

Now run composer update with all three of these libraries: php-http/httplug, php-http/client-commmon - so that it can upgrade to a new version that allows version 2 of HTTPlug - and guzzle6-adapter:

composer update php-http/httplug php-http/client-commmon php-http/guzzle6-adapter

And... cool! Now run:

php bin/console lint:container

We get no output because now our container is happy. And because a few libraries had major version upgrades, if you looked in the CHANGELOGs, you'd find that we also need one more package to truly get things to work:

composer require http-interop/http-factory-guzzle

The point is: lint:container is a free tool you can add to your continuous integration system to help catch errors earlier. The more type-hints you use in your code, the more it will catch. It's a win win!

And........ that's it! We upgraded to Symfony 4.4, fixed deprecations, upgraded to Symfony 5, jumped into some of the best new features and, ultimately, I think we became friends. Can you feel it?

If you have any upgrade problems, we're here for you in the comments. Let us know what's going on, tell us a funny story, or leave us a Symfony 5 Haiku:

Reading your comments After a long weekend break Brings joy to keyboards

Alright friends, seeya next time!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^2.0", // 2.0.6
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
        "doctrine/orm": "^2.5.11", // v2.7.2
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
        "knplabs/knp-paginator-bundle": "^5.0", // v5.0.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
        "knplabs/knp-time-bundle": "^1.8", // v1.11.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.3.0
        "nexylan/slack-bundle": "^2.1", // v2.2.1
        "oneup/flysystem-bundle": "^3.0", // 3.3.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.1
        "sensio/framework-extra-bundle": "^5.1", // v5.5.3
        "symfony/asset": "5.0.*", // v5.0.2
        "symfony/console": "5.0.*", // v5.0.2
        "symfony/dotenv": "5.0.*", // v5.0.2
        "symfony/flex": "^1.0", // v1.9.10
        "symfony/form": "5.0.*", // v5.0.2
        "symfony/framework-bundle": "5.0.*", // v5.0.2
        "symfony/mailer": "5.0.*", // v5.0.2
        "symfony/messenger": "5.0.*", // v5.0.2
        "symfony/monolog-bundle": "^3.5", // v3.5.0
        "symfony/security-bundle": "5.0.*", // v5.0.2
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.2
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "5.0.*", // v5.0.2
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "5.0.*", // v5.0.2
        "symfony/webpack-encore-bundle": "^1.4", // v1.7.2
        "symfony/yaml": "5.0.*", // v5.0.2
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "5.0.*", // v5.0.2
        "symfony/debug-bundle": "5.0.*", // v5.0.2
        "symfony/maker-bundle": "^1.0", // v1.14.3
        "symfony/phpunit-bridge": "5.0.*", // v5.0.2
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "5.0.*" // v5.0.2
    }
}