Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 113 additions & 156 deletions setup/bundles.rst
Original file line number Diff line number Diff line change
@@ -1,201 +1,158 @@
Upgrading a Third-Party Bundle for a Major Symfony Version
==========================================================

Symfony 3 was released in November 2015. Although this version doesn't contain
any new features, it removes all the backward compatibility layers included in
the previous 2.8 version. If your bundle uses any deprecated feature and it's
published as a third-party bundle, applications upgrading to Symfony 3 will no
longer be able to use it.
According to the `Symfony releases plan`_, Symfony publishes a new major version
every two years. Your third-party bundle can support more than one major version
(e.g. 5.x and 6.x), but you must apply some techniques to do so, as explained in
this article.

Allowing to Install Symfony 3 Components
----------------------------------------
Allowing to Install New Symfony Components
------------------------------------------

Most third-party bundles define their Symfony dependencies using the ``~2.N`` or
``^2.N`` constraints in the ``composer.json`` file. For example:
Consider a bundle that requires three Symfony components, locked to version ``5.4``:

.. code-block:: json

{
"require": {
"symfony/framework-bundle": "~2.7",
"symfony/finder": "~2.7",
"symfony/validator": "~2.7"
"symfony/framework-bundle": "^5.4",
"symfony/finder": "^5.4",
"symfony/validator": "^5.4"
}
}

These constraints prevent the bundle from using Symfony 3 components, which
means the bundle cannot be installed in a Symfony 3 based application. Thanks to the
flexibility of Composer dependencies constraints, you can specify more than one
major version by replacing ``~2.N`` by ``~2.N|~3.0`` (or ``^2.N`` by ``^2.N|~3.0``).

The above example can be updated to work with Symfony 3 as follows:
When Symfony releases a new major version (e.g. ``6.4``) and an application uses
it, your bundle will no longer be installable in that application. The first
step is to allow that new major version in your bundle:

.. code-block:: json

{
"require": {
"symfony/framework-bundle": "~2.7|~3.0",
"symfony/finder": "~2.7|~3.0",
"symfony/validator": "~2.7|~3.0"
"symfony/framework-bundle": "^5.4|^6.4",
"symfony/finder": "^5.4|^6.4",
"symfony/validator": "^5.4|^6.4"
}
}

.. tip::

Another common version constraint found on third-party bundles is ``>=2.N``.
You should avoid using that constraint because it's too generic (it means
that your bundle is compatible with any future Symfony version). Use instead
``~2.N|~3.0`` or ``^2.N|~3.0`` to make your bundle future-proof.

Look for Deprecations and Fix Them
----------------------------------

Besides allowing users to use your bundle with Symfony 3, your bundle must stop using
any feature deprecated by the 2.8 version because they are removed in 3.0 (you'll get
exceptions or PHP errors). The easiest way to detect deprecations is to install
the `symfony/phpunit-bridge package`_ and then run the test suite.

First, install the component as a ``dev`` dependency of your bundle:

.. code-block:: terminal

$ composer require --dev symfony/phpunit-bridge

Then, run your test suite and look for the deprecation list displayed after the
PHPUnit test report:

.. code-block:: terminal

# this command is available after running "composer require --dev symfony/phpunit-bridge"
$ ./bin/phpunit

# ... PHPUnit output

Remaining deprecation notices (3)

The "pattern" option in file ... is deprecated since version 2.2 and will be
removed in 3.0. Use the "path" option in the route definition instead ...

Twig Function "form_enctype" is deprecated. Use "form_start" instead in ...

The Symfony\Bundle\SecurityBundle\SecurityContext class is deprecated since
version 2.6 and will be removed in 3.0. Use ...

Fix the reported deprecations, run the test suite again and repeat the process
until no deprecation usage is reported.
After making the changes shown above, your bundle becomes installable in
applications using the new major version. However, it may still fail at runtime.
This happens because Symfony deprecates features in minor versions and removes
them in the next major version. If your code uses deprecated features, it will
break once those features are removed.

Useful Resources
~~~~~~~~~~~~~~~~
You can **detect deprecations** in two ways:

There are several resources that can help you detect, understand and fix the use
of deprecated features:
#. Add the ``--display-deprecations`` option when running your tests with PHPUnit
(``./bin/phpunit --display-deprecations``)
#. Install the :doc:`Symfony PHPUnit Bridge </components/phpunit_bridge>`
(``composer require --dev symfony/phpunit-bridge``) and run your tests using:
``./vendor/bin/simple-phpunit``

`Official Symfony Guide to Upgrade from 2.x to 3.0`_
The full list of changes required to upgrade to Symfony 3.0 and grouped
by component.
`SensioLabs DeprecationDetector`_
It runs a static code analysis against your project's source code to find
usages of deprecated methods, classes and interfaces. It works for any PHP
application, but it includes special detectors for Symfony applications,
where it can also detect usages of deprecated services.
`Symfony Upgrade Fixer`_
It analyzes Symfony projects to find deprecations. In addition it solves
automatically some of them thanks to the growing list of supported "fixers".
Fix the reported deprecations, rerun the test suite, and repeat the process
until no deprecations remain.

Testing your Bundle in Symfony 3
--------------------------------
Fixing Deprecations
~~~~~~~~~~~~~~~~~~~

Now that your bundle has removed all deprecations, it's time to test it for real
in a Symfony 3 application. Assuming that you already have a Symfony 3 application,
you can test the updated bundle locally without having to install it through
Composer.
Sometimes fixing a deprecation simply means using the new API instead of the
deprecated one. However, in some cases, major Symfony versions introduce larger
API changes that require conditional logic.

If your operating system supports symbolic links, instead point the appropriate
vendor directory to your local bundle root directory:
**Avoid relying on the Symfony Kernel version for compatibility checks**, as it
doesn't reflect the version of individual components and leads to fragile,
hard-to-maintain code::

.. code-block:: terminal

$ ln -s /path/to/your/local/bundle/ vendor/you-vendor-name/your-bundle-name

If your operating system doesn't support symbolic links, you'll need to copy
your local bundle directory into the appropriate directory inside ``vendor/``.

Update the Travis CI Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to running tools locally, it's recommended to set-up Travis CI service
to run the tests of your bundle using different Symfony configurations. Use the
following recommended configuration as the starting point of your own configuration:

.. code-block:: yaml

language: php
php:
- 5.3
- 5.6
- 7.0

matrix:
include:
- php: 5.3.3
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' SYMFONY_DEPRECATIONS_HELPER=max[total]=999999
- php: 5.6
env: SYMFONY_VERSION='2.7.*'
- php: 5.6
env: SYMFONY_VERSION='2.8.*'
- php: 5.6
env: SYMFONY_VERSION='3.0.*'
- php: 5.6
env: SYMFONY_VERSION='3.1.*'
- php: 5.6
env: DEPENDENCIES='dev' SYMFONY_VERSION='3.2.*@dev'

before_install:
- composer self-update
- if [ "$DEPENDENCIES" == "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
- if [ "$SYMFONY_VERSION" != "" ]; then composer --no-update require symfony/symfony:${SYMFONY_VERSION}; fi;

install: composer update $COMPOSER_FLAGS

script: phpunit

Updating your Code to Support Symfony 2.x and 3.x at the Same Time
------------------------------------------------------------------

The real challenge of adding Symfony 3 support for your bundles is when you want
to support both Symfony 2.x and 3.x simultaneously using the same code. There
are some edge cases where you'll need to deal with the API differences.

Before diving into the specifics of the most common edge cases, the general
recommendation is to **not rely on the Symfony Kernel version** to decide which
code to use::

if (Kernel::VERSION_ID < 20800) {
// code for Symfony 2.x
// ❌ don't do this - resulting code is fragile
if (Kernel::VERSION_ID <= 50400) {
// code for Symfony 5.x
} else {
// code for Symfony 3.x
// code for Symfony 6.x
}

Instead of checking the Symfony Kernel version, check the version of the specific
component. For example, the OptionsResolver API changed in its 2.6 version by
adding a ``setDefined()`` method. The recommended check in this case would be::
Instead, **use feature-based checks**, which are more accurate, robust, and
forward-compatible. For example, if a new method was added to a component in a
given version, check for that feature rather than the kernel version::

use Symfony\Component\OptionsResolver\OptionsResolver;

// ✅ this approach is stable across major versions
if (!method_exists(OptionsResolver::class, 'setDefined')) {
// code for the old OptionsResolver API
} else {
// code for the new OptionsResolver API
}

.. tip::
Testing your Bundle in Symfony Applications
-------------------------------------------

There is one case when you actually can rely on the
``Symfony\Component\HttpKernel\Kernel::VERSION_ID`` constant: when trying
to detect the version of the ``symfony/http-kernel`` component, because it
is the component where this constant is defined.
Before publishing the new version of your bundle, test it locally in a Symfony
application. You have two options:

#. Use the `Composer path repository option`_ to make the application load the
bundle from a local directory.
#. Create a symbolic link from your bundle directory to the corresponding
location inside the application's ``vendor/`` directory:

.. code-block:: terminal

$ ln -s /path/to/your/local/bundle/ vendor/your-vendor-name/your-bundle-name

Updating the GitHub CI Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to local tests, it's recommended to configure continuous integration
to test your bundle against multiple Symfony versions. Use the following example
as a starting point for your own GitHub CI configuration:

.. code-block:: yaml

.. _`symfony/phpunit-bridge package`: https://github.com/symfony/phpunit-bridge
.. _`Official Symfony Guide to Upgrade from 2.x to 3.0`: https://github.com/symfony/symfony/blob/2.8/UPGRADE-3.0.md
.. _`SensioLabs DeprecationDetector`: https://github.com/sensiolabs-de/deprecation-detector
.. _`Symfony Upgrade Fixer`: https://github.com/umpirsky/Symfony-Upgrade-Fixer
jobs:
phpunit:
strategy:
fail-fast: false
matrix:
include:
- php_version: "7.2"
symfony_version: "5.4"
stability: "stable"
- php_version: "8.1"
symfony_version: "6.4"
stability: "stable"
# ...
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.stability == 'dev' }}
steps:
- uses: actions/checkout@v5

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_version }}
coverage: none
extensions: mbstring, intl, pdo
ini-values: date.timezone=UTC

- name: symfony/flex is required to install the correct symfony version
if: ${{ matrix.symfony_version }}
run: |
composer global config --no-plugins allow-plugins.symfony/flex true
composer global require symfony/flex

- name: Configure Composer stability
run: |
composer config minimum-stability ${{ matrix.stability }}

- name: Configure Symfony version for symfony/flex
if: ${{ matrix.symfony_version }}
run: composer config extra.symfony.require "${{ matrix.symfony_version }}.*"

- name: Install dependencies
run: |
composer update ${{ matrix.composer_args }};

.. _`Symfony releases plan`: https://symfony.com/releases
.. _`Composer path repository option`: https://getcomposer.org/doc/05-repositories.md#path