Bundles and Recipes
A bundle plugs code into the kernel; a Flex recipe writes its configuration for you, and you should read what it changed.
composer recipes [<package>]A bundle is a Composer package that plugs into the kernel. It can register services, supply configuration defaults, claim a configuration alias of its own, and add console commands, routes, templates and translations. Most of Symfony itself arrives this way: FrameworkBundle, TwigBundle, SecurityBundle, DebugBundle.
config/bundles.php decides which bundles load, and in which environments. Since Symfony 4, bundles are meant for code shared between applications; your own application code lives in src/ and needs no bundle.
Flex and recipes
Symfony Flex is a Composer plugin. When you install a package, Flex looks for that package's recipe and applies it: it adds the bundle to config/bundles.php, writes a default config/packages/<alias>.yaml, may add variables to .env, routes, or Docker files, and records what it did in symfony.lock. Flex also provides aliases, so composer require logger is shorthand for composer require symfony/monolog-bundle — the same install, one name easier to remember.
Recipes come from two repositories. symfony/recipes is reviewed and runs without asking. symfony/recipes-contrib is community-maintained and Flex asks before running one.
Read it before you trust it
A recipe writes files into your project. Commit before installing, then read git status and git diff: that is the only way to know what changed. composer recipes lists what is installed and whether a newer version exists; composer recipes:update <package> applies an updated one and shows a diff first.
Example
// config/bundles.php
declare(strict_types=1);
// Maintained by Flex, but ordinary PHP you can edit. The value says which
// environments the bundle loads in; a bundle absent from an environment
// contributes no services, commands or configuration there.
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
// development-only tooling, so it cannot be reached in prod
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
// the profiler is also useful while running tests
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
];
When to use it
- Find out which files a new package added to your project before committing them.
- Disable a development-only bundle so it cannot load in production.
- Refresh a package's configuration defaults after upgrading to a new Symfony version.
More examples
Seeing exactly what an install changed
The habit worth forming. The new file and the edited bundles.php are the recipe, not Composer; reading them once tells you which configuration alias the package added and where its defaults live. Commit the recipe's output as part of the install, so the next diff is yours alone.
# start from a clean tree, so the recipe's work is the whole diff
$ git status --short
$ composer require logger # alias for symfony/monolog-bundle
$ git status --short
# M composer.json
# M composer.lock
# M config/bundles.php
# M symfony.lock
# ?? config/packages/monolog.yaml
$ git diff config/bundles.phpWhat a recipe leaves behind
A recipe gives you a starting point, not a finished configuration. Treat the file as application code: the defaults were chosen for a generic project, and the recipe still considers itself the owner of the file, which matters when you later run recipes:update.
# config/packages/monolog.yaml (written by the recipe, yours to edit)
monolog:
channels:
- deprecation
when@dev:
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ['!event']Inspecting and updating recipes
Run composer recipes after a major upgrade: configuration defaults change between Symfony versions, and an outdated recipe is a common reason a project behaves unlike the documentation. Keep --force for a file you are willing to lose.
# which recipes are installed, and which have a newer version upstream
$ composer recipes
# one recipe in detail, including the files it owns
$ composer recipes symfony/monolog-bundle
# apply a newer recipe; it shows a diff and asks before writing
$ composer recipes:update symfony/monolog-bundle
# reinstall over your files; this discards your edits to recipe-owned files
$ composer recipes:install symfony/monolog-bundle --force -v
Discussion