Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Updating the All-Important FrameworkBundle Recipe

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.

At your terminal, run:

composer recipes

As you probably know, whenever we install a new package, that package may come with a recipe that does things like add configuration files, modify certain files like .env, or add other files. Over time, Symfony makes updates to these recipes. Sometimes these are minor... like the addition of a comment in a config file. But other times, they're bigger, like renaming config keys to match changes in Symfony itself. And while you don't have to update your recipes, it's a great way to keep your app feeling like a standard Symfony app. It's also a free way to update deprecated code!

Hello recipes:update

Until recently, updating recipes was a pain. If you're not familiar, just check our "Upgrade to Symfony 5" tutorial! Yikes. But no more! Starting with Symfony Flex 1.18 or 2.1, Composer has a proper recipes:update command. It literally patches your files to the latest version... and it's awesome. Let's try it!

Run:

composer recipes:update

Oh! Before we run this, it tells us to commit everything that we've been working on. Great idea! I'll say that we are:

upgrading some code to Symfony 5.4 with Rector

git add .
git commit -m "upgrading some code to Symfony 5.4 with Rector"

Perfect! Try the recipes:update command again. The reason it wants our working copy to be clean is because it's about to patch some files... which might involve conflicts.

Let's start with symfony/framework-bundle, because this is the big one. The most important files in our project come from this recipe. I'll hit 4, clear the screen, and go!

Behind the scenes, this checks to see what the recipe looked like when we originally installed it, compares it to what the recipe looks like now, and generates a diff that it then applies to our project. In some cases, like this one, that can cause some conflicts, which is pretty cool. The best part might be that it generates a changelog containing all the pull requests that contributed to these updates. If you need to figure out why something changed, this will be your friend.

Oh, but creating the changelog requires making a bunch of API calls to GitHub. So it's possible that composer will ask you for a personal access token, like it just did for me. In some rare cases with a giant recipe like framework-bundle, if your recipe is really, really old, you might get this message even if you have given an access token to Composer. If that happens, just wait for 1 minute... then re-enter your access token. Congratulations, you just hit GitHub's per-minute API limit.

Anyways, there's the CHANGELOG. It's not usually that long, but this recipe is the most important and... well... it was horribly out-of-date. Oh, and if you have a trendy terminal like me - this is iTerm - you can click these links to jump directly into the pull request, which will live at https://github.com/symfony/recipes.

Changes to .env

Alright, let's walk through the changes this made. This is the biggest and most important recipe, so I want to cover everything.

Since I've already done my homework, I'll clear the changelog and run:

git status

Woh. It made a bunch of changes, including three conflicts. Fun! Let's go through those first. Move over and start inside .env. Let's see: apparently the recipe removed these #TRUSTED_PROXIES and #TRUSTED_HOSTS lines.

32 lines .env
... lines 1 - 15
###> symfony/framework-bundle ###
... lines 17 - 18
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
###
... lines 22 - 32

Both of these are now set in a config file. And while you could still set an environment variable and reference it from that config file, the recipe no longer ships with these comments. I'm not sure why this caused a conflict, but let's delete them.

Changes to services.yaml

The next conflict is up in config/services.yaml. This one is pretty simple. This is our config and below, the new config. The recipe removed the App\Controller\ entry. This... was never needed unless you make super-fancy controllers that do not extend AbstractController. It was removed from the recipe for simplicity. It also looks like the updated recipe reformats the exclude onto multiple lines, which is nice. So let's take their version entirely.

... lines 1 - 8
services:
... lines 10 - 18
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
... lines 25 - 30

Changes to src/Kernel.php

The final conflict is in src/Kernel.php... where you can see that our side has a bunch of code in it... and their side has nothing.

Remember how I mentioned that configureRoutes() was moved into MicroKernelTrait? Well it turns out that all of these methods were moved into MicroKernelTrait. So unless you have some custom logic - which is pretty rare - you can delete everything.

12 lines src/Kernel.php
... lines 1 - 7
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

Ok, back at the terminal, let's add those three files:

git add .env config/services.yaml src/Kernel.php

And then run

git status

to see what else the recipe update did.

Updated public/index.php, deleted bootstrap.php!

Interesting. It deleted config/bootstrap.php and modified public/index.php. Those are related. Look at the diff of index.php:

git diff --cached public/index.php

This file used to require config/bootstrap.php. And that file's job was to read and set up all the environment variables:

git diff --cached config/

Let's go check out the new public/index.php. Here it is. Now this requires some vendor/autoload_runtime.php. And the file is much shorter than before. What we're seeing is Symfony's new Runtime component in action.

10 lines public/index.php
... lines 1 - 4
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

You can check out its introduction blog post to learn more about it.

Basically, the job of booting up Symfony and loading all of the environment variables was extracted into the runtime component. But... we don't actually have that component installed yet... which is why, if we try to refresh the page, we're gonna have a bad time:

Failed to open autoload_runtime.php.

To fix this, head over to your terminal and run:

composer require symfony/runtime

This package includes a Composer plugin... so it's going to ask us if we trust it. Say "yes". Then it installs... and promptly explodes when it tries to clear the cache! Ignore that for now: we'll fix it in a few minutes. It involves updating another recipe.

But if we try our site... it works!

New Environment-Specific Configuration

Ok, we're almost done! Back at the terminal, let's see what else changed:

git status

Notice that it deleted config/packages/test/framework.yaml, but modified config/packages/framework.yaml. This is probably the most common change that you'll see when you update your recipes today.

Open config/packages/framework.yaml. At the bottom... there's a new when@test section.

... lines 1 - 19
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

Starting in Symfony 5.3, you can now add environment-specific config using this syntax. This configuration used to live inside of config/packages/test/framework.yaml. But for simplicity, the recipe deleted that file and just moved that config to the bottom of this file.

Back at the terminal, diff that file... it's hiding two other changes:

git diff --cached config/packages/framework.yaml

The recipe also changed http_method_override to false. That disables, by default, a feature that you probably weren't using anyways. It also set storage_factory_id to session.storage.factory.native. This has to do with how your session is stored. Internally, the key changed from storage_id to storage_factory_id, and it should now be configured.

Environment-Specific Routing Config

Back at the terminal, let's look at the final changes:

git status

Speaking of environment-specific config, you can do that same trick with routing files. See how it deleted config/routes/dev/framework.yaml, but added config/routes/framework.yaml? If we open up config/routes/framework.yaml, yup! It has when@dev and it imports the routes that allow us to test our error pages.

when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

This is yet another example of the recipe moving configuration out of the environment directory and into the main configuration file... just for simplicity.

The new preload.php File

Finally, the recipe added a config/preload.php file. This one is pretty simple, and it leverages PHP's preloading functionality.

... lines 1 - 2
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

Essentially, on production, if you point your php.ini, opcache.preload at this file, you'll get a free performance boost! It's that simple. Well... mostly that simple. The only other thing you need to do is restart your web server on every deploy... or PHP-FPM if you're using that. We leverage this at SymfonyCasts for a little extra performance boost.

And... phew! The biggest recipe update is done. So let's add everything and commit. Because next, more recipe updates! But with FrameworkBundle behind us, the rest will be easier and faster.

Leave a comment!

14
Login or Register to join the conversation
Steve D. Avatar
Steve D. Avatar Steve D. | posted 1 month ago

Hi

I'm now going through the tutorial again using my own Symfony 5 project but when updating the symfony/frameworkbundle recipe, my code is deleted in favour of the new code. I'm not getting the conflict (ours/theirs)

Am I missing something?

Thank you Steve

Reply

Hey Steve,

Did you notice any warnings/errors in the console when was doing that upgrade? Well, it might be so that your symfony.lock file was in not-perfect state. It happens sometimes, but that's to the "git diff" you should be able to revert your original configuration manually.

I hope this helps!

Cheers!

Reply
Steve D. Avatar

Thank you Victor. There were no errors in the console. I did start to get conflicts later on in some areas though. I pulled the code that was removed from a copy of the project so nothing has been lost.

Thank you

Reply

Hey Steve,

Awesome, glad to hear you nailed it. Yeah, it might be some edge cases probably, not sure if those are bugs or no. If you noticed a weird behaviour that looks like a bug and you have steps how to reproduced. it - feel free to report about it in Symfony.

Cheers!

Reply
Steve D. Avatar
Steve D. Avatar Steve D. | posted 1 month ago

Hi

After running "composer require symfony/runtime" I get the following error when trying to view the site.

Cannot autowire service "App\Controller\QuestionController": argument "$isDebug" of method "__construct()" is type-hinted "bool", you should configure its value explicitly.

Have I missed something?

Thank you

Steve

Reply

Hey Steve,

Did you download the course code and started from the start/ directory there? It sounds like you're missing some code in your project, most probably something that were added in previous tutorials. So, I'd recommend you to follow the tutorial this way.

But what about the actual error, make sure you have this code in your config/services.yaml


services:
# default configuration for services in *this* file
_defaults:
# ...
bind:
bool $isDebug: '%kernel.debug%'

If you have this - the autowiring should work. But also, please, make sure you typehinted that argument as "bool $isDebug" in the __construct(). And don't forget to clear the cache just in case to rule out the cache problem :)

Cheers!

Reply
Steve D. Avatar

HI Victor

I did download the course code yes and have since (in the last few mins) sussed out a couple of things. I crossed referenced the "finish" code making a few tweaks (mainly the one you have suggested) and also found that PhpStorm was using php7.4. Changing this has now got me in the right place.

Thank you so much for replying

Steve

Reply

Ah, I thought you get the error in your browser :) Yes, PhpStorm also have runtime checks that are based on the PHP version you set in config, good catch!

I'm glad you got it working ;)

Cheers!

Reply

I don't know if it's only me, but this course is already hard to follow. After the recipes:update I've got only this.


deleted: app/config/bootstrap.php
deleted: app/config/packages/test/framework.yaml
deleted: app/config/routes/dev/framework.yaml
modified: app/symfony.lock

Reply
MikkoP Avatar

Hi,
I had the same, really confusing result. You probably also have new .git -folder in your current directory, but with only objects subdir in it?

After digging deeper what happens it seems that recipes:update does not work properly when your symfony project is in a subfolder of your git project.
(Git diff in RecipePatcher assumes you are at the root of the git project, and the same with paths to .git -folders when generating and adding the blobs.)

I think I know already how this could be fixed. But as a quick fix to get further with the course, you can try working on a git repo where you are at the root folder.

Best Regards,
Mikko

1 Reply

Hey Mikko,

Oh, it might be so... I personally init Git repo for all my projects in the root dir of the Symfony project, that's why I probably have never noticed it myself. Thanks for sharing this tip! Well, I'm not sure if this behaviour would be considered as a bug, but I think it's worth to be reported upstream if it has not been reported yet.

Cheers!

Reply

Hey Julien,

This does not sound like a desired behaviour... seems something went wrong, and probably the command output has more context. Btw, what command did you execute? Well, you may want to "reset" the recipe, I suppose you're talking about symfony/framework-bundle one? You can do it with

$ composer recipe:install symfony/framework-bundle --force --reset

It will force install the latest recipe overwriting the existent data, but then with "git diff" you may revert some good changes you want to keep.

I hope this helps!

Cheers!

Reply

Hum, I did reset the head and all the files and ran

$ composer recipes:update

chose symfony/framework-bundle and got the same 4 file updates only.

Then, I did a reset again to have a clean branch and ran

$ composer recipe:install symfony/framework-bundle --force --reset

which gave something closer to the expected result

You can check my setup if you want to: https://github.com/jbonnier...

Just run

$ make local-config
$ docker compose up -d

Nginx might crash if you don't have an SSL certificate in a certain path, but you shouldn't need a web server for that.

Also all my composer commands are ran in a container with

$ docker compose exec php-cli composer ...

Reply

Hey Julien,

Yeah, not sure what happened, but "recipe:install --force --reset" should do the trick because it literally just re-install the latest recipe from scratch. A trick I use sometimes if something went wrong with "recipe:update" command.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}