[Part 1/2] Migrating to Symfony 4.0 with Flex


Our code is now deprecation free. We're good and ready to migrate from Symfony 3.4 to Symfony 4.

There's still more work to be done, however. The fun never stops.

There are two links we need concern ourselves with:

Let's start by following the major upgrade guidance.

First, we need to bump up to Symfony 4.0:

# composer.json

    "require": {
-        "symfony/symfony": "3.4.*",
+       "symfony/symfony": "^4.0",

And then if we continue following the guidance:

composer update symfony/symfony

# or

composer update symfony/symfony --with-dependencies

Well, either of these will fail. At least they do for me.

This is a rather frustrating exercise, so I'm going to skip ahead to the Symfony Flex Upgrade Guide and crack on from there.

Migrating To Symfony Flex

The long and short of this process can be boiled down to two steps:

  1. Create a new Symfony Flex project
  2. Copy / paste all the interesting bits from the old project to the new

This isn't quite as straightforward as it sounds, of course.

But let's get on with it.

Create a new empty Symfony application

Our current project is kept under git.

For the moment we're going to have to create an entirely new project, and work outside of our git repo until such time as we are happy with our progress. Once we are happy, we will create a new branch and move all the new stuff back into our original project directory.

Messy.

Anyway, here goes:

cd /tmp
composer create-project symfony/skeleton githut-flex

I'm doing this in the /tmp directory, but you should probably do this in some directory that lasts a little longer.

composer create-project symfony/skeleton githut-flex

Installing symfony/skeleton (v4.0.2)
  - Installing symfony/skeleton (v4.0.2) Loading from cache
Created project in githut-flex
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 21 installs, 0 updates, 0 removals
  - Installing symfony/flex (v1.0.49) Loading from cache
  - Installing symfony/polyfill-mbstring (v1.6.0) Loading from cache
  - Installing symfony/console (v4.0.2) Loading from cache
  - Installing symfony/routing (v4.0.2) Loading from cache
  - Installing symfony/http-foundation (v4.0.2) Loading from cache
  - Installing symfony/yaml (v4.0.2) Loading from cache
  - Installing symfony/framework-bundle (v4.0.2) Loading from cache
  - Installing symfony/http-kernel (v4.0.2) Loading from cache
  - Installing symfony/event-dispatcher (v4.0.2) Loading from cache
  - Installing psr/log (1.0.2) Loading from cache
  - Installing symfony/debug (v4.0.2) Loading from cache
  - Installing symfony/finder (v4.0.2) Loading from cache
  - Installing symfony/filesystem (v4.0.2) Loading from cache
  - Installing psr/container (1.0.0) Loading from cache
  - Installing symfony/dependency-injection (v4.0.2) Loading from cache
  - Installing symfony/config (v4.0.2) Loading from cache
  - Installing psr/simple-cache (1.0.0) Loading from cache
  - Installing psr/cache (1.0.1) Loading from cache
  - Installing symfony/cache (v4.0.2) Loading from cache
  - Installing symfony/dotenv (v4.0.2) Loading from cache
Writing lock file
Generating autoload files
Symfony operations: 4 recipes (fbe08f5a74d28474d7ff1ed5a555dcc9)
  - Configuring symfony/flex (>=1.0): From github.com/symfony/recipes:master
  - Configuring symfony/framework-bundle (>=3.3): From github.com/symfony/recipes:master
  - Configuring symfony/console (>=3.3): From github.com/symfony/recipes:master
  - Configuring symfony/routing (>=3.3): From github.com/symfony/recipes:master
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

 What's next? 

  * Run your application:
    1. Change to the project directory
    2. Execute the php -S 127.0.0.1:8000 -t public command;
    3. Browse to the http://localhost:8000/ URL.

       Quit the server with CTRL-C.
       Run composer require server for a better web server.

  * Read the documentation at https://symfony.com/doc

$ cd githut-flex

Ok sweet, we have our new app.

Merge Up composer.json

Here's the official guidance:

Merge the require and require-dev dependencies defined in your original project's composer.json file to the composer.json file of the new project (don't copy the symfony/symfony dependency, but add the relevant components you are effectively using in your project).

Here's our old deps:

    "require": {
        "php": "^7.1.2",
        "doctrine/doctrine-bundle": "^1.6",
        "doctrine/doctrine-cache-bundle": "^1.2",
        "doctrine/orm": "^2.5",
        "incenteev/composer-parameter-handler": "^2.0",
        "sensio/framework-extra-bundle": "^5.0.0",
        "symfony/monolog-bundle": "^3.1.0",
        "symfony/polyfill-apcu": "^1.0",
        "symfony/swiftmailer-bundle": "^2.6.4",
        "symfony/symfony": "^4.0",
        "twig/twig": "^1.0||^2.0",

        "guzzlehttp/guzzle": "^6.1",
        "eightpoints/guzzle-bundle": "^7.2.1",
        "symfony/web-server-bundle": "^3.4"
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0",
        "symfony/phpunit-bridge": "^3.0"
    },

And the new:

    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "symfony/console": "^4.0",
        "symfony/flex": "^1.0",
        "symfony/framework-bundle": "^4.0",
        "symfony/lts": "^4@dev",
        "symfony/yaml": "^4.0"
    },
    "require-dev": {
        "symfony/dotenv": "^4.0"
    },

A quick visual inspection of the dependency differences between our old project and new reveals we probably want eightpoints/guzzle-bundle and symfony/web-server-bundle.

There is a gotcha here, but for the moment I'm glossing over it.

We can add these two dependencies together as one liner:

composer require symfony/web-server-bundle eightpoints/guzzle-bundle

And in doing so we get to a prompt:

composer require symfony/web-server-bundle eightpoints/guzzle-bundle

Using version ^7.2 for eightpoints/guzzle-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
  - Installing symfony/expression-language (v4.0.2) Downloading: 100%         
  - Installing guzzlehttp/promises (v1.3.1) Loading from cache
  - Installing psr/http-message (1.0.1) Loading from cache
  - Installing guzzlehttp/psr7 (1.4.2) Loading from cache
  - Installing guzzlehttp/guzzle (6.3.0) Loading from cache
  - Installing eightpoints/guzzle-bundle (v7.2.1) Loading from cache
  - Installing symfony/process (v4.0.2) Downloading: 100%         
  - Installing symfony/web-server-bundle (v4.0.2) Downloading: 100%         
Writing lock file
Generating autoload files
Symfony operations: 2 recipes (5a9631b8ff1054ad71cceef8ab1e637a)
  -  WARNING  eightpoints/guzzle-bundle (>=7.0): From github.com/symfony/recipes-contrib:master
    The recipe for this package comes from the "contrib" repository, which is open to community contributions.
    Do you want to execute this recipe?
    [y] Yes
    [n] No
    [a] Yes for all packages, only for the current installation session
    [p] Yes permanently, never ask again for this project
    (defaults to n): 

I'm going to hit y here as I know and trust this bundle:

y
  - Configuring eightpoints/guzzle-bundle (>=7.0): From github.com/symfony/recipes-contrib:master
  - Configuring symfony/web-server-bundle (>=3.3): From github.com/symfony/recipes:master
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

As a side note you can see all the available Flex 'recipes' here. A recipe is a set of instructions that allows a bundle to install and auto-configure itself properly. No more copy / paste to AppKernel.php, etc.

Install the dependencies in the new project

Again the full guidance:

Install the dependencies in the new project executing composer update. This will make Symfony Flex generate all the configuration files in config/packages/

Simple enough:

composer update

Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

Nothing to see here, move along, move along.

Review the generated config/packages/*.yaml files and make any needed changes

The full guidance:

Review the generated config/packages/*.yaml files and make any needed changes according to the configuration defined in the app/config/config_*.yml file of your original project. Beware that this is the most time-consuming and error-prone step of the upgrade process.

Ok, so up until this point we were moving along at a brisk clip. Now we need to do some chore work.

First up, what's this config/packages directory all about?

ls -la config/packages

total 28
drwxrwxr-x 4 chris chris 4096 Dec 16 10:33 .
drwxrwxr-x 3 chris chris 4096 Dec 16 10:27 ..
drwxrwxr-x 2 chris chris 4096 Dec 16 10:27 dev
-rw-rw-r-- 1 chris chris  565 Dec 16 10:33 eight_points_guzzle.yaml
-rw-rw-r-- 1 chris chris  348 Dec 16 10:27 framework.yaml
-rw-rw-r-- 1 chris chris   54 Dec 16 10:27 routing.yaml
drwxrwxr-x 2 chris chris 4096 Dec 16 10:27 test

Unsurprisingly it's the directory that holds all the configuration based on our installed packages.

Well, we have been making use of only one third party bundle. This bundle - eightpoints/guzzle-bundle - brings in a new bit of autogenerated configuration. This is because eightpoints/guzzle-bundle has a defined Recipe.

To cut a long story short, we need to migrate our config from our old project to the new eight_points_guzzle.yml file:

# config/packages/eight_points_guzzle.yaml

eight_points_guzzle:
    clients:
        8p_guzzle_client:
            base_url: ""

In case you're wondering what is in the dev and test subdirectories:

ls -la config/packages/dev
total 12
drwxrwxr-x 2 chris chris 4096 Dec 16 10:27 .
drwxrwxr-x 4 chris chris 4096 Dec 16 10:40 ..
-rw-rw-r-- 1 chris chris   57 Dec 16 10:27 routing.yaml

ls -la config/packages/test
total 12
drwxrwxr-x 2 chris chris 4096 Dec 16 10:27 .
drwxrwxr-x 4 chris chris 4096 Dec 16 10:40 ..
-rw-rw-r-- 1 chris chris  138 Dec 16 10:27 framework.yaml

We're done here for GitHut, but on a real project this step takes significantly longer. At least, it has done for me.

Move over parameters.yml

The guidance:

Move the original parameters defined in app/config/parameters.*.yml to the new config/services.yaml and .env files depending on your needs.

Ahh, well we don't have any config in our parameters.yml file in the GitHut series. Therefore at first glance it appears we have nothing to do here.

But we do.

This step should also incorporate migrating your app/config/services.yml files (and any other service files you have) to the new structure. We have two custom entries in app/config/services.yml:

# app/config/services.yml

services:

    # other stuff removed for brevity

    AppBundle\Service\HttpClientInterface: '@AppBundle\Service\GuzzleHttpClient'

    GuzzleHttp\Client: "@eight_points_guzzle.client.8p_guzzle_client"

These entries need moving to the new project:

# config/services.yaml

services:

    # other stuff removed for brevity

    AppBundle\Service\HttpClientInterface: '@AppBundle\Service\GuzzleHttpClient'

    GuzzleHttp\Client: "@eight_points_guzzle.client.8p_guzzle_client"

There is a problem here that a good IDE like PhpStorm will alert you too.

AppBundle needs updating to App. We will get on to that shortly. For now, I'm leaving as is.

Are We There Yet?

There are still three more steps to go before we have successfully migrated our project from Symfony 3 to Symfony 4.

We're going to tackle these remaining steps in the next video.

Code For This Video

Get the code for this video.

Episodes