đď¸ Where should native code reside? Phalcon, Symfony, and TrueAsync at different layers of the stack
on June 27, 2026
For years Phalcon has been a rational answer for high-traffic PHP platforms: a framework delivered as a C extension, with low resource usage and a familiar MVC architecture.
Recently, during a proof-of-concept architecture project for a client, I encountered a situation quite representative of mature PHP platforms: a high-performing existing foundation, maintenance constraints, workflows that have become more complex than the simple request-response cycle, and the temptation to quickly choose a framework. The obvious question was: should we stick with a Phalcon model or rebuild around Symfony? The real question was deeper: where should native performance, business boundaries, and workflow orchestration reside?
Phalcon, Symfony, and TrueAsync are not direct competitors: they optimize different constraints at different layers of the stack. This article starts from this practical observation to offer a broader perspectiveâwithout assuming that migration is always the right answer.
Introduction - The question is no longer "which framework is faster?"
This proof of concept wasn't an isolated case. On a read-heavy platform, the dilemma often boils down to binary terms: Phalcon on one sideâa C extension, low overhead, proven MVCâand Symfony on the otherâan ecosystem, structure, and simpler recruitmentâwith TrueAsync in the background as a promise of asynchronous runtime. Each of these options had its merits. None, however, single-handedly answered the question that was gradually emerging: At which layer should performance be addressed, and at which layer should orchestration be handled?
The discussion quickly moved beyond the choice of framework. Should we stick with an architecture similar to Phalcon, where native code resides at the MVC level? Migrate to Symfony for maintainability and interoperability? Introduce TrueAsync as a runtime lever, independent of the web development decision? Or confuse framework migration with redesigning workflowsâthe most frequent mistake we see on this type of platform?
The initial question was: "Should we migrate from Phalcon to Symfony?" The fundamental question became: Where should the native code reside â and where should the boundaries and business pipelines reside?
Three responses coexist for the native speaker, each at a different level:
- Framework Layer - Phalcon optimizes router, dispatcher, ORM, cache via a C extension
- Application Layer - Symfony structures the application in pure PHP: DI, security, bundles, organizational conventions
- Runtime layer - TrueAsync aims for concurrent execution and non-blocking I/O via
ext-async
These three approaches are not mutually exclusive. But the POC primarily revealed a fourth area, often overlooked in "framework A vs. framework B" debates: where workflows reside, and how to separate business logic from the execution model that supports it. Boundaries, orchestration, and runtime are three independent decisions. Confusing themâas the initial project formulation suggestedâleads to the most expensive and least effective migrations.
At Darkwood, this is the lens we try to apply: the industry has long optimized frameworks; complex systems now expose another bottleneckâdefining boundaries and explicit orchestration. The native runtime remains an implementation detail, not the architecture. The following article generalizes what this proof of concept has crystallized into a perspective applicable beyond a specific case.
Why Phalcon made sense - and still does
Phalcon is a full-stack PHP framework with a compiled core. Written in Zephir (https://phalcon.io) and transpiled to C via the cphalcon project (https://github.com/phalcon/cphalcon), it's used like any other PHP framework: controllers, services, modules. The difference lies in the stack: an HTTP request arrives in PHP-FPM, the extension is already loaded, and the critical primitivesârouter, dispatcher, ORM, cacheâlive in the native layer. The business logic remains in PHP; the framework skeleton is optimized upstream.
For an editorial, media, or e-commerce platform, the advantages remain tangible with the right profile. Low overhead on the MVC path. Consistent model: modules, dependency injection, ORM, PHQL, Volt, cache. Read-heavy APIs powered by pre-calculated documents. Multi-surface monolith without stacking multiple layers.
Phalcon wasn't "more modern" than Symfony. It was more native in the right placeâat the framework levelâfor a specific workload profile. And for teams that have mastered this stack, that meet their SLOs, and whose product evolves little, this advantage didn't disappear overnight.
Phalcon in 2026: maintenance, management, credibility
An honest reading of Phalcon in 2026 begins with the facts - not with the assumption of a declining framework.
The 5.x branch is actively maintained. Version 5.16.0 was released in June 2026, following an accelerated release cadence in the second quarter. Commits to the 5.0.x branch are frequent; the changelog documents substantial changes: the Phalcon\Contracts layer, a new DI container, an Auth component, a Queue layer (Beanstalk, Redis, Memory, Stream), PIE support for installation, migration to Zephir 1.0, and quality assurance tools (Infection, Sonar). This is not the bare minimum maintenance of a static project.
The ecosystem has a documented foundation of credibility. The BACKERS.md file in the cphalcon repository lists industry supportersâincluding Cloudflare (also referenced on the Cloudflare sponsorships page), Algolia, and DigitalOcean (a documented open-source partnership for benchmarks and infrastructure). Open Collective and GitHub Sponsors provide modest but real community funding. Governance is based on a focused core teamâled by Nikolaos Dimopoulos and Anton Vasilievâwith structured contribution processes.
These factors don't prove that Phalcon is "better" than Symfony. They do prove that it's not an abandoned ecosystem. An industry sponsor isn't a technical guarantee; it's a sign that the project remains visible and supported by stakeholders who believe in its long-term viability.
From an architectural standpoint, cphalcon remains fundamentally Zephir/C: over a thousand .zep files, distributed as an extension via PIE or PECL. The next major release in preparation is the 7.0.x branch (PHP 8.3+), not a v6 branch in this repository. Public discussions and work on a companion PHP repository (phalcon/phalcon) suggest an evolution towards a dual-track model: a reference implementation in PHP, with optional extensions for the most expensive areasâidentified by measurement. This is an architectural maturation, not a burial of the native implementation.
| Dimensions | Phalcon (v5, 2026) | Symfony |
|---|---|---|
| Distribution | Extension C (PIE / PECL) | Composer Packages, Pure PHP |
| Main Lever | Minimal Overhead Framework | Ecosystem, Structure, Interop |
| Recruitment | Rarer profiles | Large market |
| PHP Version Upgrade | Rebuild / Repinning Extension | Semver Composer |
| Current Maintenance | Frequent releases, new features | Frequent releases, LTS |
| Best fit | Expert team, SLOs met, critical MVC performance | Growing team, numerous integrations, product churn |
The table does not designate a winner. It maps constraints.
Phalcon is not obsolete - the optimization target has changed
The wrong question is: "Is Phalcon outdated?" The right question is: "Which optimization matters most now?"
For a long time, the priorities were clear:
- Reduce the cost of the MVC dispatch and bootstrap framework
- Minimize memory and CPU overhead on the request â response path
- Serving read-heavy APIs from a high-performance monolith without multiplying runtimes
Phalcon addressed these priorities in a consistent manner. The native framework was the right lever.
Priorities have evolved - not because Phalcon "missed" something, but because systems have grown and bottlenecks have shifted:
- Distributed complexity - message buses, workers, derived models, large-scale cache invalidation
- I/O Bottlenecks - HTTP aggregation, files, network waits in workers, not just bootstrap time
- Orchestration - handler chains, cron, CLI scripts: workflow logic escapes MVC
- Onboarding and interop - recruitment, bundles, security standards, third-party integrations
- Organizational scalability - bounded contexts, progressive extraction, coexistence of stacks
On many mature platforms, the native router is no longer the measured bottleneck. Blocking I/O in workers, workflow complexity, or the human cost of maintenance are. It is this shiftânot a value judgmentâthat changes the architectural conversation.
Phalcon remains relevant when the dominant constraint is still the synchronous MVC path and the team has a firm grasp of the stack. Symfony becomes more relevant when the dominant constraint is code organization and the ecosystem over a ten-year period. TrueAsync comes into play when the dominant constraint is runtime I/O waiting â regardless of the framework chosen.
Where should native code reside?
Let's return to the central question. Three layers, three logics:
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 | |
|---|---|---|---|
| Layer | Framework | Application | Runtime |
| Target | Router, ORM, MVC, cache | Structure, DI, security, conventions | Coroutines, I/O, pools |
| Question | How to make the framework faster? | How to organize the application for the long term? | How to perform more I/O work without blocking? |
| Distribution | Extension framework (PIE / PECL) | Compose, pure PHP | Fork PHP + ext-async |
| Maturity (2026) | v5 maintained, v7 in preparation | Standard production | Experimental |
Phalcon and TrueAsync share an intuitionâbringing critical work closer to native codeâbut not the same problem. Phalcon optimizes entry into the framework. TrueAsync optimizes waiting once in the business logic. Symfony, on the other hand, doesn't focus on the native framework: it focuses on structure and interoperability.
The placement of native code is an architectural choice, not a matter of fashion. You can have native code at the framework layer without native code at the runtime. You can introduce native runtime code without abandoning Phalcon. You can structure your architecture in Symfony without TrueAsync. The combinations depend on measured constraintsânot on an "old vs. new" narrative.
Symfony - another optimization, not a universal replacement
Symfony provides a structural foundation: HttpKernel, routing, DI, security, console, cache, Messenger, Doctrine integration, and mature tooling. For a platform that needs to document, audit, and evolve its code for years, it's a credible organizing foundation.
The argument is not only technical. It is also organizational: simpler recruitment, abundant documentation, standard integrations, a clear trajectory across multiple PHP versions without depending on a framework extension compiled for each upgrade.
But Symfony optimizes a different constraint than Phalcon. It doesn't promise a router in C. It promises a maintainable, testable, integrable application - with an ecosystem that absorbs a large part of the cross-cutting complexity (auth, queues, observability, validation).
Symfony becomes the right answer when the dominant cost is no longer the microsecond of MVC bootstrap, but:
- the delivery speed for a product in evolution
- the ability to recruit and train
- Integration with external services and standards
- the clarity of application boundaries over the long term
Symfony is not automatically better. It's another optimization - and it has a real migration cost that shouldn't be underestimated.
TrueAsync - native, experimental, non-automatic runtime
TrueAsync plays a third role, independent of the framework choice. It's an experimental PHP extension (ext-async), engine integration, a libuv scheduler, coroutines, and non-blocking I/O for familiar functions (curl, files, PDO in some cases). The API offers spawn(), await(), and delay()âwithout imposing an "async color" on every function in the codebase.
Let's remain cautious about its maturity. TrueAsync is experimental: PHP 8.6+, custom build, evolving API, RFC in progress. It's neither an official PHP standard nor a drop-in replacement for Swoole or Amp. It's not "the future" of PHP eitherâit's a direction to be evaluated on specific use cases.
TrueAsync can be implemented behind a Phalcon monolith as well as behind Symfony. The native runtime does not require a framework migration. Synchronous CRUD operations in a back office generally do not. I/O-heavy workers, parallel HTTP aggregations, file pipelinesâperhaps, if the metrics justify it and if using a custom build is acceptable.
At Darkwood, we treat TrueAsync as an execution strategyâinterchangeable depending on the contextâand not as a foundational architectural decision. Architecture is the workflow and its boundaries. The async driver is simply how it's executed.
When not to migrate out of Phalcon
Migration is not always desirable. Some teams should stay on Phalcon - and that's a rational choice, not an admission of failure.
Stay if:
- The team has mastered Phalcon and productivity is high. The cost of relearning and rewriting exceeds the expected gain.
- SLOs are achieved - latency, throughput, stability - and the load profiles have not fundamentally changed
- The product evolves little - bug fixes, maintenance, no major overhaul or numerous ecosystem integrations
- Migration cost > business value - rewrites, double runs, risk of regression, feature freezes for months
- Incremental modernization is sufficient - upgrade to Phalcon 5, adoption of PIE, use of new components (Queue, Container, Contracts) without changing frameworks
In these cases, migrating to Symfony simply because "that's what everyone else is doing" is a strategic mistake. Phalcon v5 in 2026 is not the same as Phalcon 3 stuck on PHP 7: the project is evolving, the ecosystem is supported, and the native framework lever remains valid as long as it's the bottleneck.
Four possible trajectories
For a large-scale PHP platform, four solutions are plausible - none of which is the "correct" one by default:
1. Remain on Phalcon and modernize. Upgrade to v5, adopt new components, internal refactor workflows, and improve observability. Optional TrueAsync on I/O-heavy workers if the custom build is acceptable.
2. Migrate to Symfony. When ecosystem constraints, recruitment, or product evolution are paramount. Gradual migration (strangler), not a big bang. TrueAsync is optional in a later phase.
3. Hybrid Architecture. Phalcon handles critical traffic or very low-latency APIs; Symfony handles new bounded contexts or evolving surfaces. This is a deliberate coexistence, with explicit API contracts between the two worlds.
4. Introduce the native runtime independently of the framework. TrueAsync (or an async alternative) on workers and I/O pipelines, without modifying the web framework. Phalcon or Symfony in the HTTP layer; the async runtime in the execution layer.
TrueAsync is not automatically part of the response in any path except the fourth â and even then, only after measurement. It is not a migration prerequisite.
Boundaries before frameworks
Here is the thesis we defend at Darkwood, and which we find in almost all architecture projects: many "framework migrations" are actually problems of poorly defined boundaries.
A legacy monolith doesn't always suffer because its router is slow. It suffers because three business languages ââcoexist without clear boundaries: ingestion (receiving and validating an event), publishing (persisting and notifying), and reading (serving an optimized view). In the code, these three functions share the same handlers, the same services, and the same cron scripts. Changing frameworks without redrawing these boundaries only shifts the debt.
Several concepts help us to name this problem - without turning the article into a DDD catalog:
Bounded contexts. Each business area has its own vocabulary and invariants. On an editorial platform, "published article" doesn't mean the same thing on the ingestion side (the event to be processed) and on the reading side (the pre-calculated document to be served). Combining them in a single module produces the tangled handlers we see everywhere.
Domain services vs. application services. Business rules (validating content, calculating publication status) should not reside in the same place as technical coordination (invalidating a cache, calling a bus, rebuilding a read model). When everything is a "service," nothing can be tested in isolation.
Orchestration layer vs. domain layer. Orchestration handles the steps; the domain decides. "Persist, then invalidate, then rebuild" is orchestration. "Is this content publishable?" is domain. Mixing them in a controller or message handler makes it impossible to manage them independently.
CQRS and Read Models. On read-heavy platforms, the write path (canonical storage) and the read path (derived models, cache, aggregated documents) have opposing constraints. Forcing a single ORM layer to serve both creates costly trade-offs. Explicitly separating the write path and read path is not dogmaâit's a natural response to a measured workload profile.
Event-driven and event-driven consistency. After persistence, cache invalidation and read model reconstruction can be asynchronous. This is acceptableâprovided that the boundary where strong consistency ends and event-driven consistency begins is clearly defined.
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
Synchronous boundary valueâvalidation and persistenceâmust remain ACID: either the content is accepted and stored, or the operation fails cleanly. Potential boundary valueâinvalidation and reconstructionâcan tolerate a delay, provided the reading system accepts it (acceptable stale read, or versioning mechanism).
When we audit a platform for migration, the first question isn't "Phalcon or Symfony?" It's: Where are the boundaries today, and where should they be? The framework is just a container. A clean, poorly designed container is still preferable to a good one that harbors the same business confusion.
PEFT Archetype - Read-Heavy Editorial Platform Pattern
To anchor the reasoning without sticking to a real architecture, let's imagine the PEFT (High Traffic Editorial Platform) - an archetype, not a client case.
PEFT combines features common to major content platforms:
- Dominant reading path - APIs and the public web serve pre-computed views, not the hot relational database
- Event-driven ingestion - an upstream system emits publish events; workers consume, validate, and persist them
- Derived models - after persistence, invalidation and reconstruction of optimized reading structures (cache, aggregated documents)
- Multi-surface monolith - web, APIs, batch processing share a codebase, often organized into modules
- Progressive extraction - some bounded contexts are starting to detach, but the core still carries a significant portion of the traffic
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]
Read through the lens of boundaries, PEFT reveals three implicit bounded contexts:
- Ingestion - receive, validate, normalize incoming events
- Publication - maintain the canonical state, emit signals of change
- Reading - serving optimized read models, regardless of the writing scheme
The CQRS pattern is natural here: canonical storage (write model) and derived models (read models) have different lifecycles. Invalidation and reconstruction introduce an assumed potential consistency between writing and readingâacceptable as long as the SLOs for freshness are explicit.
In this archetype, architectural decisions are not based on the choice of router. They are based on:
- where the logic of ingestion and invalidation resides (entangled handlers vs explicit workflows)
- where the boundary lies between domain and orchestration
- whether the bottleneck is the synchronous MVC or the I/O of the workers
- if the product requires a major reorganization or a long stabilization period
PEFT can remain on Phalcon if the SLOs hold up and the team is stable. PEFT can migrate to Symfony if the ecosystem and churn demand it. PEFT can be hybrid. TrueAsync only comes into play if worker I/O becomes the measured bottleneckâregardless of the framework. But in all cases, redefining the boundaries precedes the technology choice.
Lessons Learned - Three POC Archetypes
The proof of concept mentioned in the introductionâa high-traffic PHP platform, high-performance legacy code, and workflows that extend beyond the MVC modelâillustrates a first archetype. It's not the only one. In our consulting and technical validation work, we regularly encounter three profiles where the right first step rarely depends on the framework. It depends on the type of dominant debt.
Archetype 1 - Legacy Monolith, Dense Business Rules. This is the case for the initial POC: a mature platform, intricate business logic, and little room for rewrite. The useful exercise was not to compare Phalcon and Symfony on paper, but to map the implicit bounded contexts, identify the boundaries of synchronous consistency, and propose a progressive extraction (strangler) context by context. The frameworkâPhalcon or Symfonyâbecame secondary: the challenge was to extract the business rules from the maze of handlers.
Archetype 2 - Greenfield Symfony. New product, growing team, numerous integrations planned. Here, the priority was domain modeling and clear modulesânot async, not native runtime. We structured the boundaries early (domain services, application services, separate reading layers) to avoid recreating, in Symfony, the spaghetti junction we were avoiding elsewhere. Messenger was sufficient for the initial workflows; explicit orchestration only came with increasing complexity.
Archetype 3 - Automation and I/O-heavy workflows. Processing chains (content enrichment, multiple API calls, file pipelines - profiles similar to some AI chains). The bottleneck wasn't the router or the domain model: it was the I/O wait and the lack of an explicit execution model. We first made the workflow readableânamed steps, isolated errors, identified sync/async boundariesâand only then evaluated TrueAsync, Amp, or Fiber depending on the environment. Introducing the async runtime before having an explicit workflow would have masked the real problem.
These three archetypes do not produce the same playbook. They share a discipline: diagnosing the dominant constraint before choosing the technology. This is what we apply at Darkwood - on Phalcon as well as on Symfony, with or without TrueAsync.
Orchestration as a primary concern
Web frameworks â Phalcon, Symfony, Laravel â are optimized for a single cycle: HTTP request â processing â response. This is their strength. It's also their blind spot.
Mature systems quickly outgrow this cycle. A publish event triggers a chain: validation, persistence, cache invalidation, read model reconstruction, notification, acknowledgment to a bus. This chain cannot be contained within a controller. It exists fragmented across message handlers, services, CLI scripts, and cron jobsâsometimes over years of development, sometimes under the pressure of production deployments that prioritized speed over structure.
We call this an orchestration problem: the explicit coordination of business steps that go beyond the request-response cycle. And we believe this problem deserves the same level of architectural consideration as the choice of framework.
A workflow-centric architecture doesn't replace MVC. It complements it. The controller remains granular: it receives, delegates, and responds. The workflow carries the multi-step logic: it names the steps, handles errors at each step, defines consistency boundaries, andâideallyâseparates the pipeline description from the execution model that runs it.
This is where we diverge from the "everything in Messenger" or "everything in a god-object service" approaches. Messenger carries messages; it doesn't natively orchestrate a multi-stage pipeline with interchangeable execution strategies. An 800-line service that orchestrates everything, but in an opaque and untestable way.
At Darkwood, we place orchestration on the same level as boundaries and framework choice. Not because a single tool solves everythingâbut because failing to name orchestration is the most frequent cause of failed migrations. You change frameworks; the handlers remain entangled; the debt follows.
Flow - separate the workflow from the execution model
It was this observation that led us to develop Flow - an open source component, not a replacement framework.
Flow exists because we've seen the same pattern repeat itself in real-world migrations: workflow logic doesn't have a natural home in Symfony or Phalcon. It ends up in anonymous handlers, cron scripts, or catch-all services. Flow gives it an architectural place: an explicit pipeline composed of Flow (the step), Job (the unit job), and Ip (the data token that passes through the pipeline).
But the most important architectural differentiator, for us, is DriverInterface.
Same workflow. Interchangeable execution model.
A publishing pipelineâvalidate, persist, invalidate, rebuildâcan run in pure synchronous mode, using PHP fibers, Amp coroutines, a ReactPHP event loop, a Swoole worker, or via TrueAsync. The business logic remains the same. Only the execution model changes. This is an infrastructure decision, not a domain decision.
Flow currently offers several drivers: FiberDriver (default, no extension), AmpDriver, ReactDriver, SwooleDriver, SpatieDriver, ParallelDriver, and TrueAsyncDriver (experimental, ext-async). Each addresses a different operational context â existing integration, dedicated worker, experimental native I/O.
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 receives the eventâvia Messenger or an internal endpointâand delegates it to Flow. Each fn() is a named step; errors can be isolated by errorJob. TrueAsync is not used first: FiberDriver is the default, measures, and then opts in to the pipeline that justifies it.
Key points we apply in production: do not mix FiberDriver and TrueAsyncDriver in the same process (TrueAsync blocks userland fibers while it is active). Do not introduce threads, channels, or TaskGroups initially â this adds operational complexity without a proven need.
Flow isn't the only possible answer. Symfony Messenger, a custom pipeline, or event-driven orchestration might suffice for simple workflows. Flow explores a more ambitious direction: making orchestration a first-order architectural primitive, with an explicit separation between the what (the pipeline) and the how (the driver).
We're not saying that Flow is the future. We're saying that explicit orchestration - with the ability to change execution strategy without rewriting the business logic - is a direction that complex systems have taught us to take seriously.
If you choose to migrate - principles, not a playbook
When the "migrate to Symfony" path is chosen - and only then - a few principles are better than a rigid five-phase plan.
Measure before you move. Identify the real bottlenecks: MVC, workflows, I/O workers, Volt/PHQL coupling, blurred boundaries. Don't migrate a high-performing framework to solve an orchestration or modeling problem.
Redefine the boundaries first. Before rewriting a module, name its bounded contexts, read models, and consistency boundaries. The strangler works by context, not by technical layer.
Strangler, not big bang. New Symfony application in parallel; progressive routing endpoint by endpoint. Preserve API contracts during coexistence.
Sync first, async later. Extract the workflows synchronously. Make the chain readable and testable before introducing TrueAsync or any other async runtime.
Opt-in on async. TrueAsync on I/O-heavy pipelines only, in a dedicated environment, with fallback possible.
Legacy patterns move differently depending on their nature: chain handlers to an explicit workflow; application modules to bundles or Feature/ folders; ad hoc cache invalidation to a named pipeline; blocking I/O in workers to an opt-in async runtime; ORM/PHQL to Doctrine - often the most expensive migration, to be dealt with separately.
A CRUD endpoint can be implemented as a simple Symfony controller. An ingestion â cache â derived model chain requires workflow consideration â and likely redefined boundaries â before any tool selection.
What not to do
Some errors recur - including when migration is not necessary.
Migrate by default because Phalcon "is old" - while v5 is maintained, SLOs hold and the team is productive.
Ignore modern Phalcon v5 - Queue, Container, Contracts, PIE - and treat the framework as stuck on an old version.
Changing frameworks without redrawing boundaries - the spaghetti monolith survives the namespace change.
Deploying TrueAsync everywhere - ignores its experimental maturity and increases operational burden.
Confusing I/O concurrency and CPU parallelism - leads to poor choices, regardless of the framework.
Transforming an orchestration library into a framework - systematic pipeline on each feature replaces one anti-pattern with another.
Presenting TrueAsync as ready for universal production - in 2026, this is a track to frame and test.
Introducing async before having an explicit workflow - hides the orchestration debt behind an execution layer.
Realistic Risks
| Risk | Mitigation |
|---|---|
| Underestimated Migration | Strangler; perimeter by endpoint; boundaries first |
| Poorly defined boundaries | Context mapping before rewrite; Explicit CQRS if read-heavy |
| Unstable TrueAsync API | Optional driver; Fiber fallback; Pinned version |
| Build custom PHP | Limit to workers / dedicated environments |
| Doctrine + competition | EM per unit of work; short transactions |
| Phalcon/Symfony coexistence | Explicit API contracts; clear routing; observability |
| Over-engineered orchestration | Pipelines on real workflows only |
Conclusion - beyond the framework, explicit orchestration
Phalcon was never a mistake. For read-heavy platforms, optimizing the framework in C was a coherent response â and remains defensible when the dominant constraint is still the MVC path.
Symfony isn't automatically better. It's a different kind of optimization: structure, ecosystem, organizational maintainability. It's relevant when these constraints outweigh the cost of the native framework.
TrueAsync is not automatically the future. It's an experimental runtime optimization â relevant for certain I/O profiles, independent of framework choice, not a prerequisite for modernization.
Phalcon, Symfony, and TrueAsync place nativeâor structured, or asyncâat different layers of the stack. But architectural competence in 2026 doesn't stop there.
The industry has spent years optimizing frameworks. The next frontier for complex systems isn't just framework performance: it's explicit orchestration, clearly defined domain boundaries, and context-sensitive execution strategies. The native runtime is an implementation lever. Orchestration is an architectural decision.
At Darkwood, we're building Flow because mature systems have taught us one thing: when workflows go beyond the request-response cycle, they need an architectural placeâand the execution logic needs to be separated from the business logic it coordinates. Flow explores a direction where orchestration becomes a first-order primitive, not an assembly of handlers and cron jobs.
The choice of framework remains important. It is only part of the equation. The real question is not just "where does the native code live?" - it is also where do the workflows live, where are the boundaries, and who bears the responsibility for orchestration in an architecture that must last ten years.
Sources
This article draws on several public sources, personal experiments and open source work on modern PHP architecture, asynchronous execution and workflow orchestration.
Frameworks and runtime
- Phalcon Framework (official site): official documentation, framework history and Zephir/C architecture.
- Symfony Framework: reference for the PHP ecosystem oriented towards maintainability and application architecture.
- TrueAsync documentation: documentation of the experimental async runtime based on
ext-async.
Darkwood References
Part of the reflections presented here comes from work carried out at Darkwood on the separation between business workflow and execution model.
- Flow - Darkwood orchestration library: Open-source implementation of business pipelines with interchangeable execution drivers (Fiber, Amp, ReactPHP, Swoole, Parallel, TrueAsync). Experimental support for TrueAsyncDriver is available as a proof of concept for exploring runtime-decoupled orchestration.
- Slidewire presentation source: The slides of the presentation associated with this article, built with Slidewire, are publicly available in this repository.