Defensive State Rollback
Persistent workers do not automatically reset global PHP process state between requests.
If an application request mutates global settings (such as the default timezone, error reporting levels, or opens output buffers that fail to close), those mutations persist and pollute subsequent worker requests.
Managed State Entities
During the request lifecycle, Leakless automatically captures and restores three critical state vectors:
1. Default Timezone Restoration
If application code or third-party packages call date_default_timezone_set():
- Leakless records the initial timezone at
startRequest(). - If mutated during the request, Leakless automatically calls
date_default_timezone_set($originalTimezone)inendRequest().
2. Output Buffer Draining
If an error or unhandled branch leaves output buffers unclosed (ob_start() without ob_end_clean() or ob_get_clean()):
- Leakless measures the initial buffer level count (
ob_get_level()) atstartRequest(). - At
endRequest(), Leakless loops and safely callsob_end_clean()until the initial buffer depth is restored, preventing stray output from leaking into future responses.
3. Error Reporting Levels
If code temporarily alters error reporting (error_reporting(E_ALL) or silences errors via @ / custom levels):
- Leakless records the original error reporting bitmask.
- Restores the original
error_reporting()level at request completion.
4. Zero-Reflection Resettables Engine (StateResetter)
Beyond global PHP settings, persistent services and static singletons often accumulate state across requests (e.g. caches, buffers, user sessions).
Leakless includes a high-performance Zero-Reflection in Hot Path state reset engine:
registerTarget($target) / Config::$resettables ──► [Warmup / Registration (Reflection once)]
│
▼
Compile native Closures
│
▼
endRequest() ─────────────────────────────────────► foreach ($closures) { $closure(); }
[Hot Path: Pure Native Closures]Supported Resettable Target Types
Anonymous Callbacks / Closures:
php$config = new Config( resettables: [ fn () => LegacyRegistry::$cache = [], ], );Conventional Reset Methods (
ResetInterface/reset()/cleanup()):phpclass CartSession { public array $items = []; public function reset(): void { $this->items = []; } }Static Reset Methods on Classes:
phpclass MetricsBuffer { public static array $logs = []; public static function resetState(): void { self::$logs = []; } }Declarative
#[ResetOnRequest]Attributes on Registered Targets:phpuse TheMattos\Leakless\Attributes\ResetOnRequest; class UserContext { // Static properties are reset when UserContext::class is in resettables: #[ResetOnRequest] public static ?string $token = null; #[ResetOnRequest(default: 'guest')] public static string $role = 'guest'; // Instance properties are reset when an object instance is in resettables: #[ResetOnRequest(default: [])] public array $permissions = []; }Register the target in your configuration:
php$config = new Config( resettables: [ // Resets static #[ResetOnRequest] properties: UserContext::class, // To reset instance properties, pass the resolved object instance: // $userContextInstance, ], );
Important: No Automatic Project Scanning
#[ResetOnRequest] defines the compilation plan for property and method resets, but it requires the target class or instance to be explicitly registered in resettables or via $leakless->registerResetTarget(). Leakless avoids expensive global AST or filesystem reflection scans on boot.
At request completion (endRequest()), Leakless automatically executes all compiled resetters without any reflection overhead.