Symfony 4.0 est sorti le 30 novembre 2017, le même jour que la 3.4. La date de sortie commune est à peu près la seule chose qu’ils ont en commun.

4.0, c’est une philosophie différente. La Symfony Standard Edition, ce point de départ monolithique qui embarquait tout et vous laissait retirer ce dont vous n’aviez pas besoin, a disparu. À sa place : un microframework qui grandit.

Flex

Symfony Flex est un plugin Composer qui change la façon dont on installe les packages Symfony. Avant Flex, ajouter un bundle impliquait : l’installer via Composer, l’enregistrer dans AppKernel.php, ajouter la config dans config/, mettre à jour le routing si nécessaire. Quatre étapes, toutes manuelles.

Avec Flex, installer un package exécute une “recette” : un ensemble d’étapes automatisées qui enregistre le bundle, génère un squelette de config et câble le routing. Installer Doctrine :

composer require symfony/orm-pack

Cette commande installe les packages, crée config/packages/doctrine.yaml, ajoute les stubs de variables d’environnement dans .env, et enregistre tout. Une commande, zéro étape manuelle.

Les recettes sont contribuées par la communauté et hébergées sur un serveur central. La qualité varie, mais pour les packages majeurs elles sont maintenues en parallèle des packages eux-mêmes.

La nouvelle structure de projet

Le layout de la Standard Edition (app/, src/, web/) est remplacé par une structure plus légère. La config se trouve dans config/, découpée par environnement. Le répertoire public s’appelle désormais public/, plus web/. Le kernel est plus petit. Les controllers sont des classes ordinaires, plus besoin d’extends Controller.

Plus important encore, le services.yaml par défaut utilise les conventions d’autowiring de la 3.3 qui rendent la configuration explicite des services largement inutile. Les nouveaux projets démarrent minimaux et grossissent en ajoutant ce dont ils ont réellement besoin.

Services privés par défaut

La plus grosse rupture de compatibilité de la 4.0 pour les apps existantes : tous les services sont privés par défaut. On ne peut plus récupérer un service directement depuis le container, il doit être injecté. C’est le bon choix du point de vue de l’injection de dépendances, mais ça casse tout ce qui utilisait $this->get('service_id') dans les controllers.

Le chemin de migration, c’est AbstractController, qui fournit les mêmes méthodes pratiques via des service locators lazy plutôt qu’un accès direct au container.

Ce qui a été supprimé

4.0 est propre parce qu’il supprime tout ce qui était déprécié en 3.4 :

  • Les anciens événements de formulaire, les anciennes interfaces de sécurité, les anciens formats de configuration
  • Le support de PHP < 7.1.3
  • Le composant ClassLoader
  • Le support ACL dans le SecurityBundle

Les suppressions sont musclées. Les apps qui ont sauté la correction de leurs dépréciations 3.4 vont souffrir. Celles qui ont fait le ménage avant ont une migration tranquille.

Symfony 4.0, c’est le reset dont le framework avait besoin. La Standard Edition avait accumulé des années de “c’est comme ça qu’on fait” que Flex balaie d’un coup.

Des variables d’environnement qui connaissent leur type

Avant 3.4 et 4.0, les variables d’environnement étaient des chaînes. Toujours. Essayer d’injecter DATABASE_PORT dans un paramètre de type int plantait silencieusement ou explosait avec une erreur de type. Le correctif était laid : caster en PHP ou éviter les paramètres typés.

4.0 embarque des processeurs de variables d’environnement qui gèrent la conversion au niveau du container :

parameters:
    app.connection.port: '%env(int:DATABASE_PORT)%'
    app.debug_mode: '%env(bool:APP_DEBUG)%'

Au-delà du casting, les processeurs peuvent décoder du base64, charger depuis des fichiers, parser du JSON, ou résoudre des paramètres du container dans une valeur. La combinaison json:file: est devenue un pattern propre pour charger des secrets depuis des fichiers montés dans des déploiements conteneurisés :

parameters:
    env(SECRETS_FILE): '/run/secrets/app.json'
    app.secrets: '%env(json:file:SECRETS_FILE)%'

Vous pouvez aussi écrire des processeurs personnalisés en implémentant EnvVarProcessorInterface et en taguant le service. Ça ressemble à de la sur-ingénierie jusqu’au jour où vous en avez besoin.

Des services taggués sans boilerplate

Avant 4.0, rassembler tous les services portant un tag donné dans un service signifiait écrire un compiler pass. Quarante lignes de PHP pour dire “donne-moi tout ce qui est tagué app.handler.”

3.4 a introduit le raccourci YAML !tagged, et 4.0 l’emporte avec lui :

services:
    App\HandlerCollection:
        arguments: [!tagged app.handler]

La collection est lazy par défaut quand elle est type-hintée en iterable, donc les services ne sont pas instanciés tant qu’on n’itère pas dessus. Ça a remplacé toute une catégorie de compiler passes qui n’existaient que pour construire des listes.

PHP comme format de configuration

YAML est la valeur par défaut depuis si longtemps que ça semble obligatoire. Ce n’est pas le cas. 4.0 embarque une configuration en PHP via une interface fluent :

// config/services.php
return function (ContainerConfigurator $container) {
    $services = $container->services()
        ->defaults()
            ->autowire()
            ->autoconfigure();

    $services->load('App\\', '../src/')
        ->exclude('../src/{Entity,Repository}');
};

La même approche fonctionne pour les routes. L’avantage pratique : autocomplétion de l’IDE, vérification des types, et vraie logique PHP dans la configuration sans la syntaxe d’interpolation %. YAML n’est pas près de disparaître, mais maintenant vous avez le choix.

Argon2i, parce que bcrypt vieillissait déjà

Symfony 3.4/4.0 a ajouté le support d’Argon2i, vainqueur du Password Hashing Competition 2015. La configuration tient en une ligne :

security:
    encoders:
        App\Entity\User:
            algorithm: argon2i

Argon2i est intégré à PHP 7.2+ et disponible via l’extension sodium sur les versions antérieures. Comme bcrypt, il se sale lui-même, inutile de gérer des colonnes de sel. Contrairement à bcrypt, il est conçu pour résister aux attaques par GPU avec une utilisation mémoire configurable. Si vous démarrez un nouveau projet sur 4.0, il n’y a vraiment aucune raison de choisir bcrypt.

La couche formulaire reçoit un thème Bootstrap 4

Le thème de formulaire Bootstrap 3 existant remonte à Symfony 2.x. Bootstrap 4 arrive comme option de premier ordre en 4.0 :

twig:
    form_themes: ['bootstrap_4_layout.html.twig']

Plus utile en pratique : les types d’input HTML5 tel et color sont désormais disponibles comme types de formulaire TelType et ColorType. Avant, il fallait écrire des types personnalisés ou surcharger des widgets bruts pour ça.

Binding de service local

Les bindings _defaults globaux s’appliquent à tous les services. Parfois on a besoin d’un binding limité à une classe ou un namespace spécifique, comme des instances de logger différentes pour des sous-systèmes différents.

4.0 supporte bind par service exactement pour ça :

services:
    App\Service\OrderService:
        bind:
            Psr\Log\LoggerInterface: '@monolog.logger.orders'

    App\Service\PaymentService:
        bind:
            Psr\Log\LoggerInterface: '@monolog.logger.payments'

Même interface, deux implémentations différentes, pas de factory, pas de configuration supplémentaire. Petite fonctionnalité, mais elle élimine toute une catégorie de bidouilles bancales.