PHP Execution Model — Shared-Nothing Architecture
debt(d7/e5/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). Misunderstanding the shared-nothing boundary manifests as subtle bugs: stale singletons under Swoole, connection exhaustion under high traffic, or session corruption under RoadRunner. No linter or static analyzer detects 'this static property will cause state bleed under persistent runtimes' — you discover it through careful code review when migrating, or through production incidents.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix indicates externalizing state to Redis/database/APCu, which typically requires refactoring session handling, singleton patterns, and connection management across multiple files. Not architectural rework, but more than a localized fix — especially when auditing all statics and singletons for Swoole migration.
Closest to 'strong gravitational pull' (b7). The execution model shapes every architectural decision in PHP: how you handle sessions, database connections, caching, and global state. Moving to persistent runtimes (Swoole/RoadRunner) requires auditing the entire codebase. This is a load-bearing concept that influences design patterns across the system, though not quite 'defines the system's shape' since PHP still functions without understanding it.
Closest to 'serious trap' (t7). The misconception states developers believe 'PHP is stateless so global variables are safe' — conflating between-request isolation with within-request safety. This contradicts mental models from persistent runtimes (Node, Go) where developers know globals persist. Worse, code that works perfectly in traditional PHP-FPM fails silently under Swoole/RoadRunner, creating a serious trap when modernizing.
Also Known As
TL;DR
Explanation
PHP's shared-nothing model means each HTTP request gets a fresh PHP interpreter state: no variables, no loaded classes, no open connections persist from previous requests unless explicitly stored externally (in Redis, a database, or APCu). This design makes horizontal scaling trivial — any server can handle any request — and eliminates entire classes of concurrency bugs. The trade-off is bootstrap cost: frameworks like Laravel or Symfony load hundreds of files, parse configurations, and build dependency injection containers on every request. OPcache eliminates the parsing cost, but the object construction cost remains. Tools like Swoole, RoadRunner, and FrankenPHP break the shared-nothing model to keep the application bootstrapped in memory between requests, trading simplicity for performance.
Common Misconception
Why It Matters
Common Mistakes
- Storing user-specific data in static class properties expecting isolation — statics are per-process, not per-request under persistent runtimes like Swoole.
- Opening database connections without connection pooling — each shared-nothing request opens and closes a connection; under high traffic this is expensive without pooling.
- Assuming session_start() works the same under RoadRunner — persistent PHP workers require custom session handlers because the request lifecycle differs.
- Not auditing singletons when migrating to Swoole — a database connection singleton that resets each request becomes a shared connection across coroutines.
Code Examples
<?php
// ❌ Assuming static state persists across requests — fine in shared-nothing PHP,
// but a bug if you migrate to Swoole/RoadRunner
class RequestCounter
{
private static int $count = 0;
public static function increment(): void
{
self::$count++; // Resets to 0 each request in traditional PHP
// But persists across requests under Swoole — counts all requests
}
}
<?php
// ✅ For persistent counters — use an external store
class RequestCounter
{
public function __construct(private Redis $redis) {}
public function increment(string $key): int
{
return $this->redis->incr($key);
}
}
// ✅ For Swoole/RoadRunner — use coroutine-local storage instead of static
// Each coroutine context gets its own storage, not shared across requests