Synapsea
Modernisation · Symfony

Migrer Zend Framework 1 vers Symfony : méthode et pièges à éviter

Zend Framework 1 est en fin de vie depuis le 28 septembre 2016. Comment transférer contrôleurs, services, accès aux données, authentification et intégrations vers Symfony, par lots, sans arrêter l'application.

Par l'équipe Synapsea · · 5 min de lecture

Zend Framework 1 a atteint sa fin de vie le 28 septembre 2016 ; sa dernière version publique est la 1.12.20. Les applications qui tournent encore dessus — et il y en a beaucoup, souvent au cœur d'un système métier — cumulent trois risques : un PHP ancien imposé par le framework, des correctifs de sécurité qui n'existent plus, et des développeurs de plus en plus rares. Voici comment nous menons la migration vers Symfony, et les erreurs que nous avons apprises à éviter.

1. Comprendre l'application ZF1 telle qu'elle est

ZF1 impose peu de structure. Avant de planifier quoi que ce soit, on cartographie :

  • les modules et contrôleurs (application/modules/*/controllers) et leurs actions réellement appelées (journaux d'accès sur quelques semaines) ;
  • les plugins de front controller et les helpers d'action : c'est souvent là que vivent l'authentification, les droits et les règles transverses ;
  • les modèles : Zend_Db_Table, requêtes SQL manuelles, ou un ORM greffé (Doctrine 1 n'est pas rare) ;
  • les ressources d'application (Bootstrap.php, application.ini) : sessions, cache, base de données, mail, traduction ;
  • les intégrations : SOAP (Zend_Soap), flux, exports planifiés, services tiers.

Le livrable est une carte : quelles fonctionnalités, quelles tables, quelles dépendances entre elles. Elle sert à découper en lots.

2. Préparer le socle : PHP et tests

Deux opérations précèdent toute ligne Symfony.

Mettre ZF1 sur un PHP récent. Le projet d'origine s'arrête à PHP 7.x ; des forks communautaires maintiennent la compatibilité avec PHP 8. Ce n'est pas une destination, c'est une étape : elle permet de faire coexister l'ancien code et Symfony dans le même processus PHP.

Écrire des tests de caractérisation. Sur ZF1, le plus efficace est souvent un jeu de tests HTTP (requête → réponse attendue, y compris les en-têtes et les redirections) sur les parcours critiques, plus quelques tests sur les exports et les traitements planifiés. Ils ne décrivent pas ce que le code devrait faire, mais ce qu'il fait.

3. La façade : faire coexister ZF1 et Symfony

On installe un squelette Symfony à côté de l'ancienne application et on fait passer toutes les requêtes par le public/index.php de Symfony. Un contrôleur de repli (« legacy bridge ») attrape les routes que Symfony ne connaît pas encore et exécute l'ancien bootstrap ZF1 :

// src/Controller/LegacyController.php
#[Route('/{path}', requirements: ['path' => '.*'], priority: -100)]
public function __invoke(): Response
{
    ob_start();
    require $this->legacyPath . '/public/index.php'; // bootstrap ZF1
    $content = ob_get_clean();

    return new Response($content, http_response_code());
}

Ce pont demande quelques précautions : partager la session (même nom de cookie, même stockage), neutraliser les exit/die de l'ancien code, et faire transiter les en-têtes. Une fois en place, chaque lot migré n'est qu'une route de plus déclarée côté Symfony.

4. Migrer lot par lot

L'ordre qui fonctionne le mieux :

  1. L'authentification et les droits, en premier et en une fois : Zend_Auth / Zend_Acl deviennent le composant Security de Symfony. Pendant la transition, un authenticator Symfony lit la session ZF1 pour que l'utilisateur reste connecté des deux côtés.
  2. Les écrans de lecture (listes, fiches) : faible risque, bonne façon de poser Doctrine sur le schéma existant sans le modifier.
  3. Les écritures par frontière fonctionnelle, avec leurs validations.
  4. Les intégrations (SOAP, exports, crons) en dernier, chacune protégée par un contrat d'interface testé.

Côté données, on conserve le schéma tant que les deux applications écrivent. Doctrine se mappe sur les tables existantes (attributs #[ORM\Table(name: 'legacy_name')]), les colonnes mal nommées sont renommées plus tard, quand ZF1 ne les touche plus.

5. Les pièges que nous avons rencontrés

  • Réécrire le schéma en même temps que le code. Deux chantiers à risque en un : impossible de savoir lequel casse. Une chose à la fois.
  • Oublier les helpers de vue et les plugins. Une règle métier cachée dans un Zend_Controller_Plugin_Abstract disparaît silencieusement si on ne l'a pas listée.
  • Les sessions. Un format de session incompatible entre les deux côtés déconnecte les utilisateurs à chaque bascule de route.
  • Les exit et les header() dans l'ancien code. Ils court-circuitent la façade ; il faut les repérer (grep, puis tests) avant de les rencontrer en production.
  • Un lot trop gros. Le premier lot doit être petit, visible et réversible : il valide le pont, pas la stratégie entière.
  • Pas de retour arrière prévu. La façade doit pouvoir renvoyer une route vers ZF1 en modifiant une seule ligne.

6. Finir : éteindre ZF1

Quand le contrôleur de repli ne reçoit plus de trafic (on le mesure), on retire l'ancien code, on migre le schéma (renommages, contraintes, types), on passe les dépendances au niveau cible — Symfony 7.4 LTS sur PHP 8.4, maintenu jusqu'en novembre 2029 pour la sécurité — et on supprime le pont. L'application est alors une application Symfony ordinaire, avec sa suite de tests et son pipeline.

Ce qu'il faut retenir

  • ZF1 est en fin de vie depuis 2016 : la migration n'est pas optionnelle, mais elle peut être progressive.
  • Cartographier, mettre sur un PHP récent, écrire des tests de caractérisation, puis commencer.
  • Une façade Symfony qui délègue à ZF1 permet de migrer route par route sans arrêt.
  • Authentification d'abord, schéma de données en dernier.

Notre offre de modernisation PHP & Symfony couvre ce chemin en trois niveaux, du diagnostic préliminaire à la migration accompagnée. Voir aussi : moderniser une application PHP sans tout réécrire.

Sources : notice de fin de vie Zend Framework 1 (dépôt zendframework/zendframework1, « End-of-Life occurred 28 Sep 2016 », dernière version 1.12.20) ; documentation Symfony (composant Security, routage, Doctrine) ; calendrier des versions Symfony (symfony.com/releases).

Une question sur ce sujet dans votre contexte ?

Un premier échange de 30 minutes, réponse sous 48 h ouvrées.

Parlons de votre projet