đïž OĂč doit vivre le code natif ? Phalcon, Symfony et TrueAsync Ă diffĂ©rentes couches de la pile
le 27 juin 2026
Pendant des années, Phalcon a été une réponse rationnelle pour les plateformes PHP à fort trafic : un framework livré en extension C, peu gourmand, MVC familier.
RĂ©cemment, lors dâun POC dâarchitecture pour un client, je me suis retrouvĂ© face Ă une situation assez reprĂ©sentative des plateformes PHP matures : une base existante performante, des contraintes de maintenance, des workflows devenus plus complexes que le cycle requĂȘte-rĂ©ponse, et la tentation de rĂ©pondre trop vite par un choix de framework. La question visible Ă©tait : faut-il rester proche dâun modĂšle Phalcon ou reconstruire autour de Symfony ? La question rĂ©elle Ă©tait plus profonde : oĂč doivent vivre la performance native, les boundaries mĂ©tier et lâorchestration des workflows ?
Phalcon, Symfony et TrueAsync ne sont pas des concurrents directs : ils optimisent des contraintes diffĂ©rentes, Ă des couches diffĂ©rentes de la pile. Cet article part de ce constat terrain pour proposer une lecture plus large - sans prĂ©supposer quâune migration est toujours la bonne rĂ©ponse.
Introduction - La question nâest plus « quel framework est plus rapide ? »
Ce POC nâĂ©tait pas isolĂ©. Sur une plateforme read-heavy, le dilemme se reformule souvent en termes binaires : Phalcon dâun cĂŽtĂ© - extension C, faible overhead, MVC Ă©prouvĂ© â, Symfony de lâautre - Ă©cosystĂšme, structure, recrutement plus simple â, avec TrueAsync en toile de fond comme promesse de runtime async. Chacune de ces options dĂ©fendait quelque chose de lĂ©gitime. Aucune ne rĂ©pondait seule Ă la question qui sâimposait peu Ă peu : Ă quelle couche doit-on adresser la performance, et Ă quelle couche lâorchestration ?
La discussion a vite dĂ©passĂ© le choix de framework. Faut-il rester sur une architecture proche de Phalcon, oĂč le natif vit au niveau MVC ? Migrer vers Symfony pour la maintenabilitĂ© et lâinteropĂ©rabilitĂ© ? Introduire TrueAsync comme levier runtime, indĂ©pendamment de la dĂ©cision web ? Ou confondre migration framework et redessin des workflows - la erreur la plus frĂ©quente que nous voyons sur ce type de plateforme ?
La question affichĂ©e Ă©tait : « Migre-t-on de Phalcon vers Symfony ? » La question de fond est devenue : oĂč doit vivre le code natif - et oĂč doivent vivre les boundaries et les pipelines mĂ©tier ?
Trois réponses coexistent pour le natif, chacune à une couche différente :
- Couche framework - Phalcon optimise routeur, dispatcher, ORM, cache via une extension C
- Couche application - Symfony structure lâapplication en PHP pur : DI, sĂ©curitĂ©, bundles, conventions dâorganisation
- Couche runtime - TrueAsync vise lâexĂ©cution concurrente et lâI/O non bloquante via
ext-async
Ces trois approches ne sâexcluent pas mutuellement. Mais le POC a surtout rĂ©vĂ©lĂ© un quatriĂšme axe, souvent nĂ©gligĂ© dans les dĂ©bats « framework A vs framework B » : oĂč vivent les workflows, et comment sĂ©parer la logique mĂ©tier du modĂšle dâexĂ©cution qui la porte. Boundaries, orchestration et runtime sont trois dĂ©cisions indĂ©pendantes. Les confondre - comme le suggĂ©rait la formulation initiale du projet - mĂšne aux migrations les plus coĂ»teuses, et les moins efficaces.
Chez Darkwood, câest cette lentille que nous essayons dâappliquer : lâindustrie a longtemps optimisĂ© les frameworks ; les systĂšmes complexes exposent aujourdâhui un autre goulot - la dĂ©finition des boundaries et lâorchestration explicite. Le runtime natif reste un dĂ©tail dâimplĂ©mentation, pas lâarchitecture. Lâarticle qui suit gĂ©nĂ©ralise ce que ce POC a cristallisĂ© en une lecture applicable au-delĂ dâun cas particulier.
Pourquoi Phalcon a eu du sens - et en a encore
Phalcon est un framework PHP full-stack dont le cĆur est compilĂ©. Ăcrit en Zephir, transpilĂ© vers du C via le projet cphalcon, il sâutilise comme nâimporte quel framework PHP : controllers, services, modules. La diffĂ©rence est dans la pile : une requĂȘte HTTP arrive dans PHP-FPM, lâextension est dĂ©jĂ chargĂ©e, et les primitives critiques - routeur, dispatcher, ORM, cache - vivent dans la couche native. Le code mĂ©tier reste en PHP ; le squelette framework est optimisĂ© en amont.
Pour une plateforme éditoriale, média ou e-commerce, les avantages restent tangibles sur le bon profil. Faible overhead sur le chemin MVC. ModÚle cohérent : modules, injection de dépendances, ORM, PHQL, Volt, cache. APIs read-heavy alimentées par des documents pré-calculés. Monolithe multi-surfaces sans empiler plusieurs stacks.
Phalcon nâĂ©tait pas « plus moderne » que Symfony. Il Ă©tait plus natif au bon endroit - au niveau framework - pour un profil de charge prĂ©cis. Et pour les Ă©quipes qui maĂźtrisent cette stack, qui atteignent leurs SLOs, et dont le produit Ă©volue peu, cet avantage nâa pas disparu du jour au lendemain.
Phalcon en 2026 : maintenance, direction, crédibilité
Une lecture honnĂȘte de Phalcon en 2026 commence par les faits - pas par lâhypothĂšse dâun framework en dĂ©clin.
La branche 5.x est activement maintenue. La version 5.16.0 a Ă©tĂ© publiĂ©e en juin 2026, aprĂšs une cadence de releases accĂ©lĂ©rĂ©e au second trimestre. Les commits sur la branche 5.0.x sont frĂ©quents ; le changelog documente des Ă©volutions substantielles : couche Phalcon\Contracts, nouveau container DI, composant Auth, couche Queue (Beanstalk, Redis, Memory, Stream), support PIE pour lâinstallation, migration vers Zephir 1.0, outillage qualitĂ© (Infection, Sonar). Ce nâest pas la maintenance minimale dâun projet figĂ©.
LâĂ©cosystĂšme dispose dâun socle de crĂ©dibilitĂ© documentĂ©. Le fichier BACKERS.md du dĂ©pĂŽt cphalcon liste des soutiens industriels - dont Cloudflare (Ă©galement rĂ©fĂ©rencĂ© sur la page des sponsorships Cloudflare), Algolia et DigitalOcean (partenariat open source documentĂ© pour benchmarks et infrastructure). Open Collective et GitHub Sponsors alimentent un financement communautaire modeste mais rĂ©el. La gouvernance repose sur une Ă©quipe core concentrĂ©e - Nikolaos Dimopoulos et Anton Vasiliev en tĂȘte - avec des processus de contribution structurĂ©s.
Ces Ă©lĂ©ments ne prouvent pas que Phalcon est « meilleur » que Symfony. Ils prouvent quâil ne sâagit pas dâun Ă©cosystĂšme abandonnĂ©. Un sponsor industriel nâest pas une garantie technique ; câest un signal que le projet reste visible et soutenu par des acteurs qui croient Ă sa pĂ©rennitĂ©.
Sur le plan architectural, cphalcon reste fondamentalement Zephir/C : plus de mille fichiers .zep, distribution en extension via PIE ou PECL. La prochaine ligne majeure en prĂ©paration est la branche 7.0.x (PHP 8.3+), pas une branche v6 dans ce dĂ©pĂŽt. Les discussions publiques et le travail sur un dĂ©pĂŽt PHP compagnon (phalcon/phalcon) suggĂšrent une Ă©volution vers un modĂšle dual-track : implĂ©mentation de rĂ©fĂ©rence en PHP, extension optionnelle pour les zones les plus coĂ»teuses - identifiĂ©es par mesure. Câest une maturation architecturale, pas un enterrement du natif.
| Dimension | Phalcon (v5, 2026) | Symfony |
|---|---|---|
| Distribution | Extension C (PIE / PECL) | Packages Composer, PHP pur |
| Levier principal | Overhead framework minimal | ĂcosystĂšme, structure, interop |
| Recrutement | Profils plus rares | Marché large |
| Montée de version PHP | Rebuild / repinning extension | Semver Composer |
| Maintenance actuelle | Releases fréquentes, features nouvelles | Releases fréquentes, LTS |
| Meilleur fit | Ăquipe experte, SLOs tenus, perf MVC critique | Ăquipe en croissance, intĂ©grations nombreuses, churn produit |
Le tableau ne désigne pas de vainqueur. Il cartographie des contraintes.
Phalcon nâest pas obsolĂšte - la cible dâoptimisation a changĂ©
La mauvaise question est : « Phalcon est-il dépassé ? » La bonne question est : « Quelle optimisation compte le plus maintenant ? »
Pendant longtemps, les priorités étaient claires :
- Réduire le coût du dispatch MVC et du bootstrap framework
- Minimiser lâoverhead mĂ©moire et CPU sur le chemin requĂȘte â rĂ©ponse
- Servir des APIs read-heavy depuis un monolithe performant sans multiplier les runtimes
Phalcon répondait à ces priorités de maniÚre cohérente. Le framework natif était le bon levier.
Les priorités ont évolué - pas parce que Phalcon a « raté » quelque chose, mais parce que les systÚmes ont grossi et les goulots ont bougé :
- Complexité distribuée - bus de messages, workers, modÚles dérivés, invalidation cache à grande échelle
- Goulots I/O - agrégation HTTP, fichiers, attentes réseau dans les workers, pas seulement le temps de bootstrap
- Orchestration - chaßnes de handlers, cron, scripts CLI : la logique workflow échappe au MVC
- Onboarding et interop - recrutement, bundles, standards de sécurité, intégrations tierces
- ĂvolutivitĂ© organisationnelle - bounded contexts, extraction progressive, coexistence de stacks
Sur beaucoup de plateformes matures, le routeur natif nâest plus le bottleneck mesurĂ©. LâI/O bloquante dans les workers, la complexitĂ© des workflows, ou le coĂ»t humain de la maintenance lâest. Câest ce dĂ©placement - pas un verdict de valeur - qui change la conversation architecturale.
Phalcon reste pertinent quand la contrainte dominante est encore le chemin MVC synchrone et que lâĂ©quipe maĂźtrise la stack. Symfony devient plus pertinent quand la contrainte dominante est lâorganisation du code et lâĂ©cosystĂšme sur dix ans. TrueAsync entre en jeu quand la contrainte dominante est lâattente I/O dans le runtime - indĂ©pendamment du framework choisi.
OĂč doit vivre le code natif ?
Revenons Ă la question centrale. Trois couches, trois logiques :
flowchart TB
subgraph layers [Couches de la pile PHP]
FrameworkLayer["Couche framework\nPhalcon extension C"]
AppLayer["Couche application\nSymfony structure"]
RuntimeLayer["Couche runtime\nTrueAsync ext-async"]
end
Question["OĂč vit le code natif ?"]
Question --> FrameworkLayer
Question --> AppLayer
Question --> RuntimeLayer
| Phalcon | Symfony | TrueAsync | |
|---|---|---|---|
| Couche | Framework | Application | Runtime |
| Cible | Routeur, ORM, MVC, cache | Structure, DI, sécurité, conventions | Coroutines, I/O, pools |
| Question | Comment rendre le framework plus rapide ? | Comment organiser lâapplication sur le long terme ? | Comment exĂ©cuter plus de travail I/O sans bloquer ? |
| Distribution | Extension framework (PIE / PECL) | Composer, PHP pur | Fork PHP + ext-async |
| Maturité (2026) | v5 maintenue, v7 en préparation | Production standard | Expérimental |
Phalcon et TrueAsync partagent une intuition - rapprocher le travail critique du natif - mais pas le mĂȘme problĂšme. Phalcon optimise lâentrĂ©e dans le framework. TrueAsync optimise lâattente une fois dans le code mĂ©tier. Symfony, lui, ne mise pas sur le natif framework : il mise sur la structure et lâinteropĂ©rabilitĂ©.
Le placement du natif est un choix dâarchitecture, pas une question de mode. On peut avoir du natif Ă la couche framework sans natif au runtime. On peut introduire du runtime natif sans quitter Phalcon. On peut structurer en Symfony sans TrueAsync. Les combinaisons dĂ©pendent des contraintes mesurĂ©es - pas dâun narratif « old vs new ».
Symfony - une autre optimisation, pas un remplacement universel
Symfony est une base structurelle : HttpKernel, routing, DI, security, console, cache, Messenger, intĂ©gration Doctrine, tooling mature. Pour une plateforme qui doit documenter, auditer et faire Ă©voluer son code pendant des annĂ©es, câest un socle organisateur crĂ©dible.
Lâargument nâest pas seulement technique. Câest aussi organisationnel : recrutement plus simple, documentation abondante, intĂ©grations standards, trajectoire claire sur plusieurs versions de PHP sans dĂ©pendre dâune extension framework compilĂ©e pour chaque montĂ©e.
Mais Symfony optimise une autre contrainte que Phalcon. Il ne promet pas un routeur en C. Il promet une application maintenable, testable, intégrable - avec un écosystÚme qui absorbe une grande partie de la complexité transverse (auth, queues, observabilité, validation).
Symfony devient la bonne rĂ©ponse quand le coĂ»t dominant nâest plus le microsecond de bootstrap MVC, mais :
- la vélocité de livraison sur un produit en évolution
- la capacité à recruter et former
- lâintĂ©gration avec des services et des standards externes
- la clarté des boundaries applicatives sur le long terme
Symfony nâest pas automatiquement meilleur. Câest une autre optimisation - et elle a un coĂ»t de migration rĂ©el quâil ne faut pas sous-estimer.
TrueAsync - runtime natif, expérimental, non automatique
TrueAsync joue un troisiĂšme rĂŽle, indĂ©pendant du choix framework. Extension PHP expĂ©rimentale (ext-async), intĂ©gration au moteur, scheduler libuv, coroutines, I/O non bloquante sur des fonctions familiĂšres (curl, fichiers, PDO dans certains cas). LâAPI propose spawn(), await(), delay() - sans imposer une « couleur async » Ă chaque fonction du codebase.
Restons mesurĂ©s sur la maturitĂ©. TrueAsync est expĂ©rimental : PHP 8.6+, build custom, API en Ă©volution, RFC en cours. Ce nâest ni un standard PHP officiel, ni un substitut drop-in Ă Swoole ou Amp. Ce nâest pas non plus « le futur » de PHP - câest une direction Ă Ă©valuer sur des cas prĂ©cis.
TrueAsync peut sâappliquer derriĂšre un monolithe Phalcon aussi bien que derriĂšre Symfony. Le runtime natif ne prĂ©suppose pas une migration framework. Le CRUD synchrone dâun back-office nâen a gĂ©nĂ©ralement pas besoin. Les workers I/O-heavy, les agrĂ©gations HTTP parallĂšles, les pipelines de fichiers - peut-ĂȘtre, si les mesures le justifient et si lâexploitation dâun build custom est acceptable.
Chez Darkwood, nous traitons TrueAsync comme une stratĂ©gie dâexĂ©cution - interchangeable selon le contexte - et non comme une dĂ©cision architecturale fondatrice. Lâarchitecture, câest le workflow et ses boundaries. Le driver async nâest que la façon dont on lâexĂ©cute.
Quand ne pas migrer hors de Phalcon
La migration nâest pas toujours souhaitable. Certaines Ă©quipes devraient rester sur Phalcon - et câest un choix rationnel, pas un aveu dâĂ©chec.
Restez si :
- LâĂ©quipe maĂźtrise Phalcon et la productivitĂ© est haute. Le coĂ»t de rĂ©apprentissage et de réécriture dĂ©passe le gain attendu
- Les SLOs sont atteints - latence, dĂ©bit, stabilitĂ© - et les profils de charge nâont pas fondamentalement changĂ©
- Le produit Ă©volue peu - corrections, maintenance, pas de refonte majeure ni dâintĂ©grations Ă©cosystĂšme nombreuses
- Le coût migration > valeur business - rewrite, double run, risque de régression, gel des features pendant des mois
- La modernisation incrémentale suffit - montée vers Phalcon 5, adoption PIE, usage des nouveaux composants (Queue, Container, Contracts) sans changer de framework
Dans ces cas, migrer vers Symfony parce que « câest ce que font les autres » est une erreur stratĂ©gique. Phalcon v5 en 2026 nâest pas la mĂȘme chose que Phalcon 3 gelĂ© sur PHP 7 : le projet bouge, lâĂ©cosystĂšme est soutenu, et le levier framework natif reste valide tant que câest lui le bottleneck.
Quatre trajectoires possibles
Pour une plateforme PHP Ă grande Ă©chelle, quatre issues sont crĂ©dibles - aucune nâest la « bonne » par dĂ©faut :
1. Rester sur Phalcon et moderniser. MontĂ©e v5, adoption des nouveaux composants, refactor interne des workflows, amĂ©lioration de lâobservabilitĂ©. TrueAsync optionnel sur les workers I/O-heavy si le build custom est acceptable.
2. Migrer vers Symfony. Quand les contraintes écosystÚme, recrutement ou évolution produit dominent. Migration progressive (strangler), pas big bang. TrueAsync optionnel en phase ultérieure.
3. Architecture hybride. Phalcon conserve le trafic critique ou les APIs à trÚs faible latence ; Symfony porte les nouveaux bounded contexts ou les surfaces en évolution. Coexistence assumée, avec des contrats API explicites entre les deux mondes.
4. Introduire le runtime natif indépendamment du framework. TrueAsync (ou une alternative async) sur les workers et pipelines I/O, sans toucher au framework web. Phalcon ou Symfony en couche HTTP ; le runtime async en couche exécution.
TrueAsync nâest automatiquement partie de la rĂ©ponse dans aucune trajectoire sauf la quatriĂšme - et mĂȘme lĂ , seulement aprĂšs mesure. Ce nâest pas un prĂ©requis de migration.
Boundaries avant frameworks
Voici la thĂšse que nous dĂ©fendons chez Darkwood et que nous retrouvons sur presque toutes les missions dâarchitecture : beaucoup de « migrations framework » sont en rĂ©alitĂ© des problĂšmes de boundaries mal dĂ©finies.
Un monolithe legacy ne souffre pas toujours parce que son routeur est lent. Il souffre parce que trois langages mĂ©tier cohabitent sans frontiĂšres claires : lâingestion (recevoir et valider un Ă©vĂ©nement), la publication (persister et notifier), la lecture (servir une vue optimisĂ©e). Dans le code, ces trois prĂ©occupations partagent les mĂȘmes handlers, les mĂȘmes services, les mĂȘmes scripts cron. Changer de framework sans redessiner ces boundaries ne fait que dĂ©placer la dette.
Quelques concepts nous aident Ă nommer ce problĂšme - sans transformer lâarticle en catalogue DDD :
Bounded contexts. Chaque zone mĂ©tier a son vocabulaire et ses invariants. Sur une plateforme Ă©ditoriale, « article publiĂ© » ne signifie pas la mĂȘme chose cĂŽtĂ© ingestion (Ă©vĂ©nement Ă traiter) et cĂŽtĂ© lecture (document prĂ©-calculĂ© Ă servir). Les confondre dans un seul module produit les handlers enchevĂȘtrĂ©s que nous voyons partout.
Services de domaine vs services dâapplication. Les rĂšgles mĂ©tier (valider un contenu, calculer un statut de publication) ne devraient pas vivre au mĂȘme endroit que la coordination technique (invalider un cache, appeler un bus, reconstruire un read model). Quand tout est un « service », rien nâest testable isolĂ©ment.
Couche orchestration vs couche domaine. Lâorchestration enchaĂźne les Ă©tapes ; le domaine dĂ©cide. « Persister puis invalider puis reconstruire » est de lâorchestration. « Ce contenu est-il publiable ? » est du domaine. Les mĂ©langer dans un controller ou un handler message rend les deux impossibles Ă faire Ă©voluer sĂ©parĂ©ment.
CQRS et read models. Sur les plateformes read-heavy, le chemin dâĂ©criture (stockage canonique) et le chemin de lecture (modĂšles dĂ©rivĂ©s, cache, documents agrĂ©gĂ©s) ont des contraintes opposĂ©es. Forcer une seule couche ORM Ă servir les deux crĂ©e des compromis coĂ»teux. SĂ©parer explicitement write path et read path nâest pas du dogme - câest une rĂ©ponse naturelle Ă un profil de charge mesurĂ©.
Event-driven et cohĂ©rence Ă©ventuelle. AprĂšs une persistence, lâinvalidation cache et la reconstruction du read model peuvent ĂȘtre asynchrones. Câest acceptable - Ă condition de nommer la frontiĂšre oĂč la cohĂ©rence forte sâarrĂȘte et oĂč la cohĂ©rence Ă©ventuelle commence.
flowchart TB
subgraph sync_boundary [Boundary synchrone]
Validate[Validation]
Persist[Persistence canonique]
Validate --> Persist
end
subgraph async_boundary [Boundary eventual]
Invalidate[Invalidation]
Rebuild[Rebuild read model]
Invalidate --> Rebuild
end
Persist -->|"événement"| Invalidate
La boundary synchrone - validation et persistence - doit rester ACID : soit le contenu est acceptĂ© et stockĂ©, soit lâopĂ©ration Ă©choue proprement. La boundary Ă©ventuelle - invalidation et reconstruction - peut tolĂ©rer un dĂ©lai, Ă condition que le systĂšme de lecture le assume (stale read acceptable, ou mĂ©canisme de versioning).
Quand nous auditons une plateforme en vue de migration, la premiĂšre question nâest pas « Phalcon ou Symfony ? ». Câest : oĂč sont les boundaries aujourdâhui, et oĂč devraient-elles ĂȘtre ? Le framework nâest quâun contenant. Un mauvais contenant propre reste prĂ©fĂ©rable Ă un bon contenant qui recĂšle la mĂȘme confusion mĂ©tier.
Archetype PEFT - pattern plateforme éditoriale read-heavy
Pour ancrer le raisonnement sans coller Ă une architecture rĂ©elle, imaginons la PEFT (Plateforme Ăditoriale Ă Fort Trafic) - un archetype, pas un cas client.
PEFT combine des traits communs aux grandes plateformes de contenu :
- Chemin de lecture dominant - les APIs et le web public servent des vues pré-calculées, pas la base relationnelle à chaud
- Ingestion événementielle - un systÚme amont émet des événements de publication ; des workers consomment, valident, persistent
- ModÚles dérivés - aprÚs persistence, invalidation et reconstruction de structures de lecture optimisées (cache, documents agrégés)
- Monolithe multi-surfaces - web, APIs, traitements batch partagent une base de code, souvent organisée en modules
- Extraction progressive - certains bounded contexts commencent Ă se dĂ©tacher, mais le cĆur porte encore une part significative du trafic
flowchart LR CMS[Source de contenu] --> Bus[Bus de messages] Bus --> Workers[Workers ingestion] Workers --> Store[(Stockage canonique)] Workers --> Cache[(ModÚles dérivés)] Cache --> APIs[APIs read-heavy] APIs --> Clients[Clients]
Lue avec la lentille des boundaries, PEFT révÚle trois bounded contexts implicites :
- Ingestion - recevoir, valider, normaliser les événements entrants
- Publication - persister lâĂ©tat canonique, Ă©mettre les signaux de changement
- Lecture - servir des read models optimisĂ©s, indĂ©pendamment du schĂ©ma dâĂ©criture
Le pattern CQRS y est naturel : le stockage canonique (write model) et les modĂšles dĂ©rivĂ©s (read models) ont des cycles de vie diffĂ©rents. Lâinvalidation et la reconstruction introduisent une cohĂ©rence Ă©ventuelle assumĂ©e entre Ă©criture et lecture - acceptable tant que les SLOs de fraĂźcheur sont explicites.
Sur cet archetype, les décisions architecturales ne se jouent pas sur le choix du routeur. Elles se jouent sur :
- oĂč vit la logique dâingestion et dâinvalidation (handlers enchevĂȘtrĂ©s vs workflows explicites)
- oĂč passe la frontiĂšre entre domaine et orchestration
- si le bottleneck est le MVC synchrone ou lâI/O des workers
- si le produit exige une réorganisation profonde ou une stabilisation longue
PEFT peut rester sur Phalcon si les SLOs tiennent et lâĂ©quipe est stable. PEFT peut migrer vers Symfony si lâĂ©cosystĂšme et le churn produit le commandent. PEFT peut hybrider. TrueAsync nâentre en scĂšne que si les workers I/O deviennent le goulot mesurĂ© - quel que soit le framework. Mais dans tous les cas, redessiner les boundaries prĂ©cĂšde le choix technologique.
Retours dâexpĂ©rience - trois archĂ©types de POC
Le POC Ă©voquĂ© en introduction - plateforme PHP Ă fort trafic, hĂ©ritage performant, workflows qui dĂ©bordent du MVC - illustre un premier archĂ©type. Ce nâest pas le seul. Dans nos missions de conseil et validations techniques, nous retrouvons rĂ©guliĂšrement trois profils oĂč le bon premier mouvement dĂ©pend rarement du framework. Il dĂ©pend du type de dette dominante.
ArchĂ©type 1 - Monolithe legacy, rĂšgles mĂ©tier denses. Câest le cas du POC dâouverture : plateforme mature, logique mĂ©tier enchevĂȘtrĂ©e, peu de marge pour un rewrite. Lâexercice utile nâa pas Ă©tĂ© de comparer Phalcon et Symfony sur le papier, mais de cartographier les bounded contexts implicites, dâidentifier les frontiĂšres de cohĂ©rence synchrone, et de proposer une extraction progressive (strangler) contexte par contexte. Le framework - Phalcon ou Symfony - est devenu secondaire : lâenjeu Ă©tait de sortir les rĂšgles mĂ©tier du magma des handlers.
ArchĂ©type 2 - Greenfield Symfony. Nouveau produit, Ă©quipe en croissance, intĂ©grations nombreuses prĂ©vues. Ici, la prioritĂ© Ă©tait la modĂ©lisation du domaine et des modules clairs - pas lâasync, pas le runtime natif. Nous avons structurĂ© les boundaries tĂŽt (domain services, application services, couches de lecture distinctes) pour Ă©viter de recrĂ©er, en Symfony, le monolithe spaghetti quâon fuyait ailleurs. Messenger a suffi pour les premiers workflows ; lâorchestration explicite nâest venue quâavec la complexitĂ©.
ArchĂ©type 3 - Automatisation et workflows I/O-heavy. ChaĂźnes de traitement (enrichissement de contenu, appels API multiples, pipelines de fichiers - profils proches de certaines chaĂźnes IA). Le goulot nâĂ©tait ni le routeur ni le modĂšle de domaine : câĂ©tait lâattente I/O et lâabsence de modĂšle dâexĂ©cution explicite. Nous avons dâabord rendu le workflow lisible - Ă©tapes nommĂ©es, erreurs isolĂ©es, boundaries sync/async identifiĂ©es - puis seulement Ă©valuĂ© TrueAsync, Amp ou Fiber selon lâenvironnement. Introduire le runtime async avant dâavoir un workflow explicite aurait masquĂ© le vrai problĂšme.
Ces trois archĂ©types ne produisent pas le mĂȘme playbook. Ils partagent une discipline : diagnostiquer la contrainte dominante avant de choisir la technologie. Câest ce que nous appliquons chez Darkwood - sur Phalcon comme sur Symfony, avec ou sans TrueAsync.
Lâorchestration comme prĂ©occupation de premier ordre
Les frameworks web - Phalcon, Symfony, Laravel - sont optimisĂ©s pour un cycle : requĂȘte HTTP â traitement â rĂ©ponse. Câest leur force. Câest aussi leur angle mort.
Les systÚmes matures dépassent vite ce cycle. Un événement de publication déclenche une chaßne : validation, persistence, invalidation cache, reconstruction de read model, notification, accusé vers un bus. Cette chaßne ne tient pas dans un controller. Elle vit éclatée entre handlers message, services, scripts CLI et tùches cron - parfois sur des années de développement, parfois sous des pressions de mise en production qui ont privilégié la vitesse sur la structure.
Nous appelons cela un problĂšme dâorchestration : la coordination explicite dâĂ©tapes mĂ©tier qui dĂ©passent le cycle requĂȘte-rĂ©ponse. Et nous pensons que ce problĂšme mĂ©rite le mĂȘme niveau de rĂ©flexion architecturale que le choix du framework.
Une architecture workflow-centric ne remplace pas le MVC. Elle le complĂšte. Le controller reste fin : il reçoit, dĂ©lĂšgue, rĂ©pond. Le workflow porte la logique multi-Ă©tapes : il nomme les Ă©tapes, gĂšre les erreurs par Ă©tape, dĂ©finit les boundaries de cohĂ©rence, et - idĂ©alement - sĂ©pare la description du pipeline du modĂšle dâexĂ©cution qui le fait tourner.
Câest lĂ que nous divergeons des approches « tout dans Messenger » ou « tout dans un service god-object ». Messenger transporte des messages ; il nâorchestrre pas nativement un pipeline multi-Ă©tapes avec stratĂ©gies dâexĂ©cution interchangeables. Un service de 800 lignes qui fait tout orchestre, mais de façon opaque et non testable.
Chez Darkwood, nous plaçons lâorchestration au mĂȘme niveau que les boundaries et le choix framework. Pas parce quâun seul outil rĂ©sout tout - mais parce que ne pas nommer lâorchestration est la cause la plus frĂ©quente des migrations ratĂ©es. On change de framework ; les handlers restent enchevĂȘtrĂ©s ; la dette suit.
Flow - sĂ©parer le workflow du modĂšle dâexĂ©cution
Câest cette observation qui nous a conduits Ă dĂ©velopper Flow - un composant open source, pas un framework de remplacement.
Flow existe parce que nous avons vu, sur des migrations rĂ©elles, le mĂȘme pattern se rĂ©pĂ©ter : la logique workflow nâa pas de maison naturelle dans Symfony ou Phalcon. Elle finit dans des handlers anonymes, des scripts cron ou des services fourre-tout. Flow lui donne une place architecturale : un pipeline explicite composĂ© de Flow (lâĂ©tape), Job (le travail unitaire) et Ip (le jeton de donnĂ©es qui traverse le pipeline).
Mais le différenciateur architectural le plus important, pour nous, est DriverInterface.
MĂȘme workflow. ModĂšle dâexĂ©cution interchangeable.
Un pipeline de publication - valider, persister, invalider, reconstruire - peut sâexĂ©cuter en synchrone pur, en fibers PHP, en coroutines Amp, sur une event loop ReactPHP, dans un worker Swoole, ou via TrueAsync. Le mĂ©tier ne change pas. Seul le modĂšle dâexĂ©cution change. Câest une dĂ©cision dâinfrastructure, pas de domaine.
Flow propose aujourdâhui plusieurs drivers : FiberDriver (dĂ©faut, pas dâextension), AmpDriver, ReactDriver, SwooleDriver, SpatieDriver, ParallelDriver, et TrueAsyncDriver (expĂ©rimental, ext-async). Chacun rĂ©pond Ă un contexte dâexploitation diffĂ©rent - intĂ©gration existante, worker dĂ©diĂ©, I/O natif expĂ©rimental.
use Flow\Driver\FiberDriver;
use Flow\Driver\TrueAsyncDriver;
use Flow\Flow\Flow;
use Flow\Ip;
// Le workflow est stable ; seul le driver change selon l'environnement
$driver = TrueAsyncDriver::isSupported()
? new TrueAsyncDriver()
: new FiberDriver();
$flow = (new Flow(job: new ValidateMessage(), driver: $driver))
->fn(new Persist()) // domaine : boundary synchrone
->fn(new InvalidateCache()) // orchestration
->fn(new RebuildReadModel()); // I/O potentiellement async
$flow(new Ip($message));
$flow->await();
Symfony reçoit lâĂ©vĂ©nement - via Messenger ou un endpoint interne - et dĂ©lĂšgue Ă Flow. Chaque fn() est une Ă©tape nommĂ©e ; les erreurs peuvent ĂȘtre isolĂ©es par errorJob. On ne commence pas par TrueAsync : FiberDriver par dĂ©faut, mesure, puis opt-in sur le pipeline qui le justifie.
Points dâattention que nous appliquons en production : ne pas mĂ©langer FiberDriver et TrueAsyncDriver dans le mĂȘme processus (TrueAsync bloque les fibers userland tant quâil est actif). Ne pas introduire threads, channels ou TaskGroup dans un premier temps - complexitĂ© dâexploitation sans besoin dĂ©montrĂ©.
Flow nâest pas la seule rĂ©ponse possible. Symfony Messenger, un pipeline maison, ou une orchestration par Ă©vĂ©nements peuvent suffire sur des workflows simples. Flow explore une direction plus ambitieuse : faire de lâorchestration une primitive architecturale de premier ordre, avec sĂ©paration explicite entre le quoi (le pipeline) et le comment (le driver).
Nous ne disons pas que Flow est le futur. Nous disons que lâorchestration explicite - avec la possibilitĂ© de changer de stratĂ©gie dâexĂ©cution sans réécrire le mĂ©tier - est une direction que les systĂšmes complexes nous ont appris Ă prendre au sĂ©rieux.
Si vous choisissez de migrer - principes, pas playbook
Quand la trajectoire « migrer vers Symfony » est choisie - et seulement alors - quelques principes valent mieux quâun plan en cinq phases rigide.
Mesurer avant de bouger. Identifier les vrais bottlenecks : MVC, workflows, I/O workers, couplage Volt/PHQL, boundaries floues. Ne pas migrer un framework performant pour rĂ©soudre un problĂšme dâorchestration ou de modĂ©lisation.
Redessiner les boundaries dâabord. Avant de réécrire un module, nommer ses bounded contexts, ses read models, ses frontiĂšres de cohĂ©rence. Le strangler fonctionne par contexte, pas par couche technique.
Strangler, pas big bang. Nouvelle application Symfony en parallÚle ; routage progressif endpoint par endpoint. Préserver les contrats API pendant la coexistence.
Sync dâabord, async ensuite. Extraire les workflows en synchrone. Rendre la chaĂźne lisible et testable avant dâintroduire TrueAsync ou tout autre runtime async.
Opt-in sur lâasync. TrueAsync sur les pipelines I/O-heavy uniquement, en environnement dĂ©diĂ©, avec repli possible.
Les patterns legacy se dĂ©placent diffĂ©remment selon leur nature : les handlers en chaĂźne vers un workflow explicite ; les modules applicatifs vers des bundles ou dossiers Feature/ ; lâinvalidation cache ad hoc vers un pipeline nommĂ© ; lâI/O bloquante dans les workers vers un runtime async opt-in ; lâORM/PHQL vers Doctrine - souvent la migration la plus coĂ»teuse, Ă traiter Ă part.
Un endpoint CRUD se porte en controller Symfony simple. Une chaĂźne ingestion â cache â modĂšle dĂ©rivĂ© mĂ©rite une rĂ©flexion workflow - et probablement des boundaries redessinĂ©es avant tout choix dâoutil.
Ce quâil ne faut pas faire
Certaines erreurs reviennent - y compris quand la migration nâest pas nĂ©cessaire.
Migrer par dĂ©faut parce que Phalcon « fait vieux » - alors que v5 est maintenue, les SLOs tiennent et lâĂ©quipe est productive.
Ignorer Phalcon v5 moderne - Queue, Container, Contracts, PIE - et traiter le framework comme figé sur une version ancienne.
Changer de framework sans redessiner les boundaries - le monolithe spaghetti survit au changement de namespace.
DĂ©ployer TrueAsync partout - ignore sa maturitĂ© expĂ©rimentale et alourdit lâexploitation.
Confondre concurrence I/O et parallélisme CPU - mÚne à de mauvais choix, quel que soit le framework.
Transformer une lib dâorchestration en framework - pipeline systĂ©matique sur chaque feature remplace un anti-pattern par un autre.
PrĂ©senter TrueAsync comme prĂȘt pour la production universelle - en 2026, câest une piste Ă cadrer et Ă tester.
Introduire lâasync avant dâavoir un workflow explicite - masque la dette dâorchestration derriĂšre une couche dâexĂ©cution.
Risques réalistes
| Risque | Mitigation |
|---|---|
| Migration sous-estimĂ©e | Strangler ; pĂ©rimĂštre par endpoint ; boundaries dâabord |
| Boundaries mal définies | Cartographie contexts avant rewrite ; CQRS explicite si read-heavy |
| API TrueAsync instable | Driver optionnel ; repli Fiber ; version épinglée |
| Build PHP custom | Limiter aux workers / environnements dédiés |
| Doctrine + concurrence | EM par unité de travail ; transactions courtes |
| Coexistence Phalcon / Symfony | Contrats API explicites ; routage clair ; observabilité |
| Sur-ingénierie orchestration | Pipelines sur workflows réels uniquement |
Conclusion - au-delĂ du framework, lâorchestration explicite
Phalcon nâa jamais Ă©tĂ© une erreur. Pour des plateformes read-heavy, optimiser le framework en C Ă©tait une rĂ©ponse cohĂ©rente - et reste dĂ©fendable quand la contrainte dominante est encore le chemin MVC.
Symfony nâest pas automatiquement meilleur. Câest une optimisation diffĂ©rente : structure, Ă©cosystĂšme, maintenabilitĂ© organisationnelle. Pertinent quand ces contraintes dominent le coĂ»t du framework natif.
TrueAsync nâest pas automatiquement le futur. Câest une optimisation runtime expĂ©rimentale - pertinente sur certains profils I/O, indĂ©pendante du choix framework, pas un prĂ©requis de modernisation.
Phalcon, Symfony et TrueAsync placent le natif - ou la structure, ou lâasync - Ă des couches diffĂ©rentes de la pile. Mais la compĂ©tence architecturale, en 2026, ne sâarrĂȘte pas lĂ .
Lâindustrie a passĂ© des annĂ©es Ă optimiser les frameworks. La frontiĂšre suivante, pour les systĂšmes complexes, nâest pas seulement la performance framework : câest lâorchestration explicite, les boundaries de domaine clairement tracĂ©es, et les stratĂ©gies dâexĂ©cution interchangeables selon le contexte. Le runtime natif est un levier dâimplĂ©mentation. Lâorchestration est une dĂ©cision dâarchitecture.
Chez Darkwood, nous construisons Flow parce que les systĂšmes matures nous ont appris une chose : quand les workflows dĂ©passent le cycle requĂȘte-rĂ©ponse, il faut leur donner une place architecturale - et sĂ©parer la logique dâexĂ©cution du mĂ©tier quâelle coordonne. Flow explore une direction oĂč lâorchestration devient une primitive de premier ordre, pas un assemblage de handlers et de cron.
Le choix framework reste important. Il nâest quâune partie de lâĂ©quation. La vraie question nâest pas seulement « oĂč vit le code natif ? » - câest aussi oĂč vivent les workflows, oĂč passent les boundaries, et qui porte la responsabilitĂ© de lâorchestration dans une architecture qui doit tenir dix ans.
Sources
Cet article sâappuie sur plusieurs sources publiques, expĂ©rimentations personnelles et travaux open source autour de lâarchitecture PHP moderne, de lâexĂ©cution asynchrone et de lâorchestration de workflows.
Frameworks et runtime
- Phalcon Framework (site officiel) : documentation officielle, historique du framework et architecture Zephir/C.
- Symfony Framework : rĂ©fĂ©rence pour lâĂ©cosystĂšme PHP orientĂ© maintenabilitĂ© et architecture applicative.
- TrueAsync documentation : documentation du runtime async expérimental basé sur
ext-async.
Références Darkwood
Une partie des rĂ©flexions prĂ©sentĂ©es ici provient de travaux menĂ©s chez Darkwood autour de la sĂ©paration entre workflow mĂ©tier et modĂšle dâexĂ©cution.
- Flow - Darkwood orchestration library : ImplĂ©mentation open source de pipelines mĂ©tier avec drivers dâexĂ©cution interchangeables (Fiber, Amp, ReactPHP, Swoole, Parallel, TrueAsync). Le support expĂ©rimental de TrueAsyncDriver y est disponible comme preuve de concept pour explorer lâorchestration dĂ©couplĂ©e du runtime.
- Slidewire presentation source : Les slides de la présentation associée à cet article, construites avec Slidewire, sont disponibles publiquement dans ce dépÎt.