$reason, 'owner' => $owner, 'created_at' => date('c'), 'expires_at' => date('c', time() + max(30, $ttlSeconds)), ]; self::writeState($payload); } public static function unfreeze(?string $owner = null): void { $state = self::state(); if ($owner !== null && isset($state['owner']) && $state['owner'] !== $owner) { return; } $path = self::statePath(); if (is_file($path)) { @unlink($path); } } public static function state(): array { $path = self::statePath(); if (!is_file($path)) { return []; } $state = json_decode((string)file_get_contents($path), true); if (!is_array($state)) { @unlink($path); return []; } $expiresAt = strtotime((string)($state['expires_at'] ?? '')); if ($expiresAt !== false && $expiresAt < time()) { @unlink($path); return []; } return $state; } public static function isFrozen(): bool { return self::state() !== []; } public static function shouldBlock(string $method, string $uri, bool $isCronOrCli): bool { if (!self::isFrozen()) { return false; } if ($isCronOrCli) { return true; } $method = strtoupper($method); if (in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) { return false; } $path = parse_url($uri, PHP_URL_PATH) ?: ''; return !str_starts_with($path, '/superuser/replication'); } private static function writeState(array $state): void { $path = self::statePath(); $dir = dirname($path); if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) { throw new RuntimeException('Could not create write-freeze directory.'); } $tempPath = tempnam($dir, 'write-freeze-'); if ($tempPath === false) { throw new RuntimeException('Could not create write-freeze temp file.'); } try { file_put_contents($tempPath, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL, LOCK_EX); if (!rename($tempPath, $path)) { throw new RuntimeException('Could not atomically replace write-freeze state.'); } } finally { if (is_file($tempPath)) { @unlink($tempPath); } } } private static function statePath(): string { $root = defined('WD') ? WD : dirname(__DIR__); return $root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'application-write-freeze.json'; } }