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!

  • 2020-08-04 weaverryan

    Hey Kiuega A !

    > Thank you for your reply ! I see, are you happy with the behat + mink combot? I have never used them yet.

    Definitely! But mostly it's because I like the BDD of Behat. Mink can actually be made to work with Selenium (which is what we use for historical reasons) or also Panther, though I haven't tried this yet - https://github.com/robertfa...

    > I am wondering if it would be possible to create a new custom environment where I could take over all the configurations of the dev environment, but defining the test database

    Yes, you are thinking *exactly* correct. What you really want is a "panther" environment that shares all the config with the test environment *except* for the session storage. And, hmm. I think the best way to do this *might* actually be to *not* create a new environment, but, kind of "fake it". Basically, use the "test" environment but with an extra flag that we can use to change the session storage. So, here is how:

    A) Set PANTHER_APP_ENV=panther, which means that when the Symfony web server starts, APP_ENV will actually equal panther
    B) In bootstrap.php, we're going to "short circuit" things. Before the .env files are loaded - so here - https://github.com/symfony/... - add this code:


    if (isset($_SERVER['APP_ENV']) && $_SERVER['APP_ENV'] === 'panther') {
    $_SERVER['APP_ENV'] = 'test';
    $_SERVER['USING_PANTHER'] = true;
    }

    This changes the environment back to "test" before anything loads. BUT, we set a separate flag. We'll use this in Kernel.php

    C) In src/Kernel.php, we will read the USING_PANTHER to load an extra config file. Right after this line - https://github.com/symfony/...


    if (isset($_SERVER['USING_PANTHER']) && $_SERVER['USING_PANTHER']) {
    $container->import('../config/{packages}/panther/*.yaml');
    }

    D) Finally, we'll add a new config/packages/panther/framework.yaml file with this:


    framework:
    session:
    # if you have a custom value in your config/packages/framework.yaml, then use that value here instead
    storage_id: session.storage.native


    I may have messed up a detail or three, but this is the idea. Let me know if this makes sense.

    Cheers!

  • 2020-08-03 Kiuega A

    Thank you for your reply ! I see, are you happy with the behat + mink combot? I have never used them yet.

    Then, ok thanks for the explanations, it's clear now. But now in my use case which is the following:

    Make real changes in the test database, authenticating each category of users and performing whatever actions they are supposed to be able to do in order to see if nothing is broken.

    The solution I have to use for that is therefore not to define the variable PANTHER_APP_ENV (it will be at dev by default), and in my .env, switch the development database for the test database when I perform my tests. So what allows me to use Panther without any worries, it's simple, but not professional.

    Now, since indeed, in test environment, there is no storage session, I would have to find something else.

    I am wondering if it would be possible to create a new custom environment where I could take over all the configurations of the dev environment, but defining the test database. Thus, I will be able to use Panther, by defining the PANTHER_APP_ENV on the new environment created, which would therefore support the storage session, and the test database! What do you think ?

    Hope my answer is clear forgive me I am using google translate to communicate

  • 2020-08-03 weaverryan

    Hey Kiuega A!

    First, I still haven't used Panther - just because we already use Behat + Mink in our site. So, I may not *fully* answer your question correctly ;).

    Behind the scenes, Panther uses the PHP binary to start a built-in web server. Then, it makes requests to that running web server. And, indeed, the PANTHER_APP_ENV controls the environment that this built-in web server will be using. A few things about this:

    A) Yes, if you set PANTHER_APP_ENV=test, then it would read your .env.test file (and config/packages/test/*). So if you have a custom database set up for the test environment, then your "running app" would use this. However, *also* realize that inside your test functions, if you boot Symfony's kernel and using the entity manager to insert data, that is an *entirely* different process and could use a different environment (though it uses the test environment by default).

    B) The reason that PANTHER_APP_ENV is set to "panther" and not "test" by default can be found in the PR that added this to the recipe: https://github.com/symfony/... - as you can see, you actually *don't* want the "test" environment to be used with Panther because sessions aren't enabled correctly in the test environment. The reason is that normally the test environment (with functional tests) is meant for you to make "fake requests" (inside the same PHP process) from your PHPUnit test function into Symfony's kernel. There is no *real* request, so fake session handling can be used. But with Panther, there *is* a real web request. This may be why authentication gives you nothing - there is no session storage, so even if you authenticate, your successful authentication would never be set to a session cookie correctly.

    Let me know if that helps!

    Cheers!

  • 2020-08-01 Kiuega A

    Hello ! Thank you for this superb training!
    On the other hand there is a change you mentioned, the environment variable `PANTHER_APP_ENV = panther`

    This means that if we set it equal to "test", Panther will rely on our test database (if defined) when it does its tests?

    This is great because before that it was always relying on my ** dev ** database which was frustrating!

    I had a chance to try, but unfortunately when I try to authenticate a user who is supposed to belong to the test database when I run Panther in ** test ** mode, it doesn't seem to work, authentication gives absolutely nothing. Have you ever experienced this?

  • 2020-07-28 Victor Bocharsky

    Hey Daniel!

    Thank you for your feedback! You made our day! :)

    Cheers!

  • 2020-07-28 Daniel

    Thanks guys. This course was one of the best ever in my humble opinion. Cheers!

  • 2020-04-20 Diego Aguiar

    That's great to hear. Symfony devs do a great job trying to make as smooth as possible to upgrade the framework. Sometimes is not that easy as we would like to but with tutorials like this one, the upgrading process is even fun to do :)

  • 2020-04-18 Holger Maerz

    some chapter earlier it was mentioned in the tutorial that some packages in 5.0 are split. So it was easy to know where to search for more details eg. packagist. worked like a swiss knife. I never had a major upgrade of symfony that quick and easy

  • 2020-04-17 Diego Aguiar

    Hey Holger Maerz

    Thanks! I hope that wasn't a big problem :)

  • 2020-04-17 Holger Maerz

    Thank you for this tutorial. It worked like a charm. I had only minor issues due to now splitted packages as symfony/security and renaming a kernel variable by Gedmo logger configuration.