DEV Community

Hantsy Bai
Hantsy Bai

Posted on

Upgrade to Symfony v7

Symfony has just released Symfony 7.0, there are a lot of breaking changes introduced in this version.

In this post, we will use Symfony Rest Example project as an example, and show how I upgrade to v7 step by step.

Backup Existing Codes

Firstly, let's create a GIT tag on the existing codes, and back up the project.

git tag v6.x 
git push origin v6.x
Enter fullscreen mode Exit fullscreen mode

For those still use Symfony 6.x, please check the tag v6.x or download a copy from the project tag page.

Create a new branch to prepare the upgrading work.

git checkout -b v7
Enter fullscreen mode Exit fullscreen mode

Upgrade Symfony Packages to v7

Import the project source codes into an IDE(PHPStorm or NetBeans IDE) or VSCode( with PHP support).

Open composer.json file, change all symfony packages version to 7.0.*.

{
  "name": "hantsy/symfony-rest-sample",
  "description": "Restful APIs examples built with Symfony 7 and PHP 8",
  "authors": [
    {
      "name": "Hantsy Bai",
      "email": "hantsy@gmail.com"
    }
  ],
  "type": "project",
  "license": "GPL-3.0-or-later",
  "minimum-stability": "stable",
  "prefer-stable": true,
  "require": {
    "php": ">=8.2",
    "ext-ctype": "*",
    "ext-iconv": "*",
    "composer/package-versions-deprecated": "^1.11.99.4",
    "doctrine/annotations": "^2.0",
    "doctrine/doctrine-bundle": "^2.4",
    "doctrine/doctrine-migrations-bundle": "^3.1",
    "doctrine/orm": "^2.9",
    "phpdocumentor/reflection-docblock": "^5.2",
    "symfony/asset": "7.0.*",
    "symfony/console": "7.0.*",
    "symfony/dotenv": "7.0.*",
    "symfony/expression-language": "7.0.*",
    "symfony/flex": "^2.0.1",
    "symfony/framework-bundle": "7.0.*",
    "symfony/monolog-bundle": "^3.7",
    "symfony/property-access": "7.0.*",
    "symfony/property-info": "7.0.*",
    "symfony/runtime": "7.0.*",
    "symfony/serializer": "7.0.*",
    "symfony/uid": "7.0.*",
    "symfony/validator": "7.0.*",
    "symfony/yaml": "7.0.*"
  },
  "config": {
    "optimize-autoloader": true,
    "preferred-install": {
      "*": "dist"
    },
    "allow-plugins": true,
    "sort-packages": true
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "App\\Tests\\": "tests/"
    }
  },
  "replace": {
    "symfony/polyfill-ctype": "*",
    "symfony/polyfill-iconv": "*",
    "symfony/polyfill-php72": "*"
  },
  "scripts": {
    "auto-scripts": {
      "cache:clear": "symfony-cmd",
      "assets:install %PUBLIC_DIR%": "symfony-cmd"
    },
    "post-install-cmd": [
      "@auto-scripts"
    ],
    "post-update-cmd": [
      "@auto-scripts"
    ],
    "test": "php bin/phpunit"
  },
  "conflict": {
    "symfony/symfony": "*"
  },
  "extra": {
    "symfony": {
      "allow-contrib": false,
      "require": "7.0.*"
    }
  },
  "require-dev": {
    "dama/doctrine-test-bundle": "^8.0",
    "doctrine/doctrine-fixtures-bundle": "^3.4",
    "phpunit/phpunit": "^9.5",
    "symfony/browser-kit": "7.0.*",
    "symfony/css-selector": "7.0.*",
    "symfony/maker-bundle": "^1.34",
    "symfony/phpunit-bridge": "7.0.*"
  }
}
Enter fullscreen mode Exit fullscreen mode

We remove sensio/framework-extra-bundle and nelmio/cors-bundle from the package list due to the compatibility with the new v7.

  • The former is deprecated in v7 and not recommended in new projects. We will use new API to replace them.
  • The later does not release a Symfony v7 compatible version at the moment.

Remove composer.lock, try to run composer install command in a terminal to install new packages and rerun the built-in recipes for this project.

Refresh Codes with New APIs

There are a few classes that need to align with the new APIs in Symfony v7.

Firstly, we used ArgumentResolver in sensio/framework-extra-bundle to convert parameters to Uuid. In Symfony v7, the ValueResolver is the replacement. More details please check Extending Action Argument Resolving.

And there is a built-in UidValueResolver can be used to convert the parameter to Uuidv4, so we can give up our custom Uuid converter, and switch to use the official one.

Simply remove the existing src/ParamConverter/UuidParamConverter.

Next, let's update the BodyValueResolver and QueryParamValueResolver. The original ArgumentValueResolverInterface is removed in v7, the replacement is ValueResolverInterface, which is similar to the old ArgumentValueResolverInterface, but there is no supports method to override.

We adjust the existing codes slightly to make it work seamlessly.

class QueryParamValueResolver implements ValueResolverInterface, LoggerAwareInterface
{
    public function __construct()
    {
    }

    private LoggerInterface $logger;

    /**
     * @inheritDoc
     */
    public function resolve(Request $request, ArgumentMetadata $argument): iterable
    {
        if (!$this->supports($request, $argument)) return [];
        ...
    }    
Enter fullscreen mode Exit fullscreen mode

We reuse supports method to determine if skip the execution of argument resolving. Similarly, we adjust the BodyValueResolver as expected.

Rerun composer install, we get an error like the following.

!!  Symfony\Component\ErrorHandler\Error\FatalError {#307
!!    #message: "Compile Error: Declaration of App\Annotation\Delete::getMethods() must be compatible with Symfony\Component\Routing\Attribute\Route::getMethods(): array"
!!    #code: 0
!!    #file: "D:\hantsylabs\symfony5-sample\src\Annotation\Delete.php"
!!    #line: 11
!!    -error: array:4 [
!!      "type" => 64
!!      "message" => "Declaration of App\Annotation\Delete::getMethods() must be compatible with Symfony\Component\Routing\Attribute\Route::getMethods(): array"
!!      "file" => "D:\hantsylabs\symfony5-sample\src\Annotation\Delete.php"
!!      "line" => 11
!!    ]
!!  }
!!  PHP Fatal error:  Declaration of App\Annotation\Delete::getMethods() must be compatible with Symfony\Component\Routing\Attribute\Route::getMethods(): array in D:\hantsylabs\symfony5-sample\src\Annotation\Delete.php on line 11
Enter fullscreen mode Exit fullscreen mode

In Symfony v7, all methods and properties in classes require type declaration. For developers, it is good to write type safe codes.

Symfony provides a simple script to upgrade existing codes and apply type declaration automatically.

vendor/bin/patch-type-declarations
Enter fullscreen mode Exit fullscreen mode

More information about the type declaration, check Symfony 7.0 Type Declarations.

Rerun composer install, we get an new error like the following.

!!  In Loader.php line 63:
!!                                                                                 
!!    Cannot load resource "../../src/Controller/". Make sure there is a loader   
!!    supporting the "annotation" type.   
Enter fullscreen mode Exit fullscreen mode

We should use the new attributes config to replace the legacy annotations config for loading controllers.

  1. Remove config/routes/annotations.yaml.
  2. Create a new file config/routes/attributes.yaml, fill in the following content.

    controllers:
        resource:
            path: ../../src/Controller/
            namespace: App\Controller
        type: attribute
    
    kernel:
        resource: App\Kernel
        type: attribute
    

More information about Route, check Creating Routes as Attributes.

Now the composer install command should be executed successfully.

When running the tests, there are several deprecated warning info.

The Get, Post, Put, Delete, etc. that we created as examples of demonstrating attributes now raise warning info like the Route could be final in future. Thus means in a further version, these custom attributes will not work.

  1. Remove the entire src\Annotations folder.
  2. Open the PostController, use original Route attribute instead.

Note, if you use Route from Symfony\Component\Routing\Annotation\Route, change it to use the new Symfony\Component\Routing\Attribute\Route instead.

The left another two warning info are from Doctrine configuration.

Open config/packages/doctrine.yaml, add the following two lines.

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'
        use_savepoints: true  // add this line
        ...
    orm:
        auto_generate_proxy_classes: true
        enable_lazy_ghost_objects: true // add this line
        ...
Enter fullscreen mode Exit fullscreen mode

I have not researched the new changes in Doctrine, these configuration is provided in the warning info.

Now run the tests, all tests should be passed.

Upgrade to PHPUnit 10

Run the following command to update PHPUnit to 10.x.

composer recipes:update symfony/phpunit-bridge
Enter fullscreen mode Exit fullscreen mode

Then update the PHPUnit packages to ^10.0.

composer install
Enter fullscreen mode Exit fullscreen mode

When running the tests, there are some deprecation information in the test result.

Run the following command to migrate the configuration to PHPUnit 10 compatible format.

vendor/bin/phpunit --migrate-configuration
Enter fullscreen mode Exit fullscreen mode

You will find the Symfony test listener is removed by this script, because listener is not a valid element in PHPUnit 10 configuration. Let's wait for a new replacement in a further version.

Manually add a source element into the phpunit.xml.dist.

  <source>
    <include>
        <directory suffix=".php">src</directory>
    </include>
  </source>
Enter fullscreen mode Exit fullscreen mode

Now run the tests again, all tests should be passed.

Update PHP versions on Github Actions

Finally, we should update the PHP version in Github actions workflow file, drop PHP 8.1 from the versoin list, and add PHP 8.3 as build environment. PHP 8.1 is no longer supported in the new Symfony v7.

The working codes is available via my Github, check out the symfony-rest-sample and explore it yourself.

Top comments (0)