In the old Symfony 2.0.x days, at a time when Composer did not exist, upgrading a project from one minor version of Symfony to the next one was not that easy. You had to merge the changes that were made in the deps and the deps.lock files into yours. The diff was made of changes from Symfony itself, but also from "core" Symfony bundles that were included in the Standard Edition. And of course, if you had other third-party dependencies, you had to upgrade them by yourself (finding the right version to use for each of them). It worked well even if merging things was sometimes a nightmare, but it was unfortunately possible to install dependencies that did not work well together.

Fast forward to the Composer world.

Composer is used by Symfony since version 2.1, but as we were the very first adopter, and because Composer and the Symfony ecosystem were not stable yet, it was still awkward. The recommended minimum stability was dev, and as such, whenever you ran composer update, you got the bleeding edge of your dependencies. In a way to reduce the pain, we provided the composer.lock file for the Standard Edition... which you could use when upgrading your project. But again, if you had other dependencies, merging the locked deps was a pain. We were basically using Composer as the old deps files, except that Composer would ensure that all dependency versions worked well together.

Fast forward to Symfony 2.3.

As of Symfony 2.3, the minimum stability level has been raised to stable. That was possible because the Symfony ecosystem as a whole is much more mature and because almost all good/useful/maintained/popular bundles have stable releases with a good configuration for their dependencies. If you know of a cool bundle or PHP library that still requires a dev minimum stability, talk to the lead developer and convince him to tag a stable release.

Because we are now using stable versions of all dependencies by default, we do not need to keep around a composer.lock file as a template. That's why the diff between two versions of the Symfony Standard Edition can be empty... which is disturbing but totally fine.

So, what is the workflow to upgrade your Symfony 2.3 projects? This is ridiculously simple, so simple that some developers are lost! Let me explain the upgrade workflow for Symfony 2.3+:

1
$ composer update

That's all there is to it. No need to do anything else. And of course, the resulting composer.lock file should be stored in your project repository so that all developers working with you on the project will install the exact same versions of the projects dependencies.

Notice that there is nothing related to Symfony here. So, upgrading from one minor version of Symfony to the next one is no different than upgrading any other dependencies.

You don't need to track changes or upgrades. And because Symfony 2.3 is a Long-Term Support release with a strong "Don't break backward compatibility" motto, you can run composer update with confidence. Symfony is just another dependency of your project, like any other ones.

That's the short explanation and you can stop here. If you want to learn more, keep reading while I show you how it works in practice.

Let's say that you created a new Symfony 2.3.0 project last week with the following command:

1
$ composer create-project -n symfony/framework-standard-edition path/ 2.3.0

When running this command, Composer creates a new path/ directory, where it downloads the Symfony Standard Edition version 2.3.0. It then installs all the dependencies referenced in the composer.json file by running composer install:

Installing symfony/framework-standard-edition (v2.3.0)
  - Installing symfony/framework-standard-edition (v2.3.0)
    Loading from cache

Created project in path/
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing jdorn/sql-formatter (v1.2.9)
    Loading from cache

  - Installing doctrine/lexer (v1.0)
    Loading from cache

  - Installing doctrine/annotations (v1.1.1)
    Loading from cache

  - Installing doctrine/collections (v1.1)
    Loading from cache

  - Installing doctrine/cache (v1.0)
    Loading from cache

  - Installing doctrine/inflector (v1.0)
    Loading from cache

  - Installing doctrine/common (2.4.0-RC3)
    Loading from cache

  - Installing doctrine/dbal (2.3.4)
    Loading from cache

  - Installing psr/log (1.0.0)
    Loading from cache

  - Installing twig/twig (v1.13.1)
    Loading from cache

  - Installing symfony/icu (v1.2.0)
    Loading from cache

  - Installing symfony/symfony (v2.3.0)
    Loading from cache

  - Installing doctrine/doctrine-bundle (v1.2.0)
    Loading from cache

  - Installing twig/extensions (v1.0.0)
    Loading from cache

  - Installing kriswallsmith/assetic (v1.1.1)
    Loading from cache

  - Installing symfony/assetic-bundle (v2.3.0)
    Loading from cache

  - Installing swiftmailer/swiftmailer (v5.0.0)
    Loading from cache

  - Installing symfony/swiftmailer-bundle (v2.3.0)
    Loading from cache

  - Installing monolog/monolog (1.5.0)
    Loading from cache

  - Installing symfony/monolog-bundle (v2.3.0)
    Loading from cache

  - Installing sensio/distribution-bundle (v2.3.0)
    Loading from cache

  - Installing sensio/framework-extra-bundle (v2.3.0)
    Loading from cache

  - Installing sensio/generator-bundle (v2.3.0)
    Loading from cache

  - Installing incenteev/composer-parameter-handler (v2.0.0)
    Loading from cache

  - Installing doctrine/orm (2.3.4)
    Loading from cache

    Skipped installation of bin/doctrine for package doctrine/orm: name conflicts with an existing file
    Skipped installation of bin/doctrine.php for package doctrine/orm: name conflicts with an existing file
kriswallsmith/assetic suggests installing leafo/lessphp (Assetic provides the integration with the lessphp LESS compiler)
kriswallsmith/assetic suggests installing leafo/scssphp (Assetic provides the integration with the scssphp SCSS compiler)
kriswallsmith/assetic suggests installing ptachoire/cssembed (Assetic provides the integration with phpcssembed to embed data uris)
kriswallsmith/assetic suggests installing leafo/scssphp-compass (Assetic provides the integration with the SCSS compass plugin)
monolog/monolog suggests installing mlehner/gelf-php (Allow sending log messages to a GrayLog2 server)
monolog/monolog suggests installing raven/raven (Allow sending log messages to a Sentry server)
monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server)
monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))
monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)
Writing lock file
Generating autoload files
Creating the "app/config/parameters.yml" file.
Clearing the cache for the dev environment with debug true
Installing assets using the hard copy option
Installing assets for Symfony\Bundle\FrameworkBundle into web/bundles/framework
Installing assets for Acme\DemoBundle into web/bundles/acmedemo
Installing assets for Sensio\Bundle\DistributionBundle into web/bundles/sensiodistribution

As you can see in the output, Composer installed Symfony version 2.3.0, and it used the same version for all the Symfony bundles lister in the Composer file.

Now, to upgrade to Symfony 2.3.1, you can run the following command:

1
$ composer update symfony/symfony

And the output will be something along the lines of:

Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing symfony/symfony (v2.3.0)
  - Installing symfony/symfony (v2.3.1)
    Loading from cache

Writing lock file
Generating autoload files
Updating the "app/config/parameters.yml" file.
Clearing the cache for the dev environment with debug true
Installing assets using the hard copy option
Installing assets for Symfony\Bundle\FrameworkBundle into web/bundles/framework
Installing assets for Acme\DemoBundle into web/bundles/acmedemo
Installing assets for Sensio\Bundle\DistributionBundle into web/bundles/sensiodistribution

As you can see, Composer updated the symfony/symfony package only. That's because the Symfony bundles are configured as being compatible with any version of Symfony 2.3, so updating them to 2.3.1 was not required.

But if I had run the update command without any arguments:

1
$ composer update

Composer would have updated all dependencies to their latest stable versions:

Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing symfony/symfony (v2.3.0)
  - Installing symfony/symfony (v2.3.1)
    Loading from cache

  - Removing symfony/swiftmailer-bundle (v2.3.0)
  - Installing symfony/swiftmailer-bundle (v2.3.1)
    Loading from cache

  - Removing sensio/distribution-bundle (v2.3.0)
  - Installing sensio/distribution-bundle (v2.3.1)
    Loading from cache

  - Removing sensio/framework-extra-bundle (v2.3.0)
  - Installing sensio/framework-extra-bundle (v2.3.1)
    Loading from cache

  - Removing sensio/generator-bundle (v2.3.0)
  - Installing sensio/generator-bundle (v2.3.1)
    Loading from cache

Writing lock file
Generating autoload files
Updating the "app/config/parameters.yml" file.
Clearing the cache for the dev environment with debug true
Installing assets using the hard copy option
Installing assets for Symfony\Bundle\FrameworkBundle into web/bundles/framework
Installing assets for Acme\DemoBundle into web/bundles/acmedemo
Installing assets for Sensio\Bundle\DistributionBundle into web/bundles/sensiodistribution

Note

Actually, I cheated a bit when writing this article. When you ask Composer to install the Symfony Standard Edition at version 2.3.0, it downloads the package and the run composer install. But it then installed Symfony version 2.3.1 instead of 2.3.0, why? First, even if the versions are the same, there is no relation between version 2.3.0 of Symfony and version 2.3.0 of the Standard Edition. It just happens that we synchronize our releases for simplicity (something that we might stop doing at some point). Then, because the Standard Edition does not come with a composer.lock file, because I ran this command after version 2.3.1 was released, and because the constraint in the composer.json file is 2.3.*, Composer actually installed Symfony version 2.3.1, which was the latest stable version that matched the constraint in the composer file.

So, when adding a dependency to your project (be it Symfony, a Symfony component, a Symfony bundle, or any other PHP library for that matter), you have several options:

  • Be conservative and force one specific version: "symfony/symfony": "2.3.0";
  • Be restrictive and force a range of versions: "symfony/symfony": ">=2.3.0,<2.4-dev";
  • Be confident and use any future stable versions: "symfony/symfony": "~2.3".

The last option is only recommended if the dependency follows the semantic versioning scheme and if you trust the developer update policy (never break backward compatibility in a minor version).

To sum up, here are my golden rules:

  • Use stable as the minimum stability level in your composer.json file (use the @dev suffix as a suffix if one of your dependency is not stable yet);
  • Define good version constraints in your composer.json file (see the three possible choices above);
  • Run composer update with confidence whenever you want to upgrade your project dependencies to their latest stable version.