Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Lifecycle Modes & Legacy Apps

The four runtime modes, when each one fits, and an honest look at why coroutine-legacy is still experimental.

You will learn

  • The four modes App::mode() selects — and the one knob that distinguishes them
  • A decision guide: which mode for new apps, Symfony/Laravel, and unmodified WordPress
  • What coroutine-legacy actually does — and what it requires (ext-zealphp)
  • Why coroutine-legacy is flagged experimental right now — the real reasons
  • The dividing line: Composer/PSR-4 apps work; pure require_once apps belong in legacy-cgi

One call picks the runtime: App::mode()

Everything about how a request is handled — whether $_GET/$_SESSION are real, whether requests run concurrently, whether each .php file gets its own process — is decided by a single call near the top of app.php. The trade-off behind every mode is the same one you met in Lesson 3: process-wide superglobals are not safe across concurrent coroutines. Each mode resolves that tension differently.

Mode Superglobals Concurrency Best for
App::mode('coroutine')
the default
Off — use $g->get / $g->session ✅ Full coroutine concurrency New apps. Fastest, cleanest, real in-request parallelism.
App::mode('mixed') ✅ Real $_GET/$_SESSION Sequential (one request at a time per worker) Symfony / Laravel bridges — the stable PHP-FPM drop-in.
App::mode('legacy-cgi') ✅ Real, fully isolated per process Sequential, process-per-file (warm pool, ~1–3 ms) Unmodified WordPress / Drupal — maximum compatibility.
App::mode('coroutine-legacy')
experimental
✅ Real, isolated per coroutine ✅ Full coroutine concurrency Composer/PSR-4 legacy apps that want real superglobals and concurrency.

The first three are stable and well-trodden. mixed is the closest apples-to-apples PHP-FPM replacement; legacy-cgi is the conservative choice for apps that assume “this script runs alone.” The fourth, coroutine-legacy, is the ambitious one — and the rest of this lesson is an honest look at it.

What coroutine-legacy is trying to do

It is a compatibility runtime: it lets traditional request-style PHP — the PHP-FPM “fresh state per request” mental model — run under OpenSwoole coroutine concurrency, with every request-state primitive isolated per coroutine. Real $_GET, $_POST, $_SESSION, $GLOBALS, function-local static $x, and require_once state all behave as if each request had its own process — while dozens of requests run concurrently on one worker.

It pulls this off with ext-zealphp, a small C extension that hooks OpenSwoole’s scheduler and snapshots/restores per-coroutine state across every yield. App::mode('coroutine-legacy') auto-enables the whole isolation stack:

app.php — coroutine-legacy is one call (ext-zealphp required)
use ZealPHP\App;

// Requires ext-zealphp: pie install sibidharan/ext-zealphp
App::mode(App::MODE_COROUTINE_LEGACY);
// equivalent to: superglobals(true) + isolation(coroutine) + silentRedeclare(true)
//                + includeIsolation(true) + coroutineGlobalsIsolation(true)
//                + coroutineStaticsIsolation(true)

$app = App::init('0.0.0.0', 8080);
$app->run();

The dividing line: how is your app’s class graph loaded?

The single best predictor of whether coroutine-legacy will be smooth is how your app loads its classes — because that determines whether the class graph can be warmed before concurrency arrives.

Your appClass loadingRecommendation
Symfony, Laravel, Slim, any modern Composer app PSR-4 autoload (each class required once per worker) coroutine-legacy works — warm with App::preloadClassmap() (run composer dump-autoload --optimize). Validated across a 12-app sweep.
Unmodified WordPress, Drupal 7, phpBB Pure require_once bootstrap, no autoloader ⚠️ Use legacy-cgi instead. It is process-isolated, has no coroutine race at all, and is the correct race-free home for these apps today.
Brand-new ZealPHP app n/a — you write $g->get code ✅ Plain coroutine mode. You don’t need any of this.

You want to run an unmodified WordPress install (pure require_once bootstrap, no autoloader) on ZealPHP today. Which mode?

Key Takeaways

  • One call — App::mode() — selects the runtime; the axis behind every mode is the superglobals-vs-concurrency trade-off.
  • coroutine (default) for new apps; mixed for stable PHP-FPM-style Composer legacy; legacy-cgi for unmodified WordPress/Drupal.
  • coroutine-legacy is the experimental compatibility runtime: real superglobals + coroutine concurrency, via per-coroutine isolation in ext-zealphp (required).
  • It's experimental because it needs a C extension, the “just works” promise is conditional on warming the class graph, there are open WordPress-teardown issues, and opcache can fight class re-declaration.
  • The dividing line is class loading: PSR-4/Composer apps can be warmed and work; pure require_once apps (classic WordPress) belong in legacy-cgi for now.
  • When unsure, pick mixed — the stable apples-to-apples PHP-FPM swap.