getMessage(); } return [ 'step' => $stepName, 'status' => $exitCode === 0 ? 'ok' : 'failed', 'exit_code' => $exitCode, 'vendor_warnings_ignored' => max(0, self::vendorWarningsIgnoredTotal() - $warningsBefore), 'error' => $error, ]; } /** * @param callable():int $runner * @return array{busy: bool, message: string|null, exit_code: int} */ public static function withFileLock(string $lockFile, string $busyMessage, callable $runner): array { $lockDir = dirname($lockFile); if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) { throw new RuntimeException("Failed to create lock directory: {$lockDir}"); } $lock = fopen($lockFile, 'c'); if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) { if (is_resource($lock)) { fclose($lock); } return [ 'busy' => true, 'message' => $busyMessage, 'exit_code' => 0, ]; } try { return [ 'busy' => false, 'message' => null, 'exit_code' => $runner(), ]; } finally { flock($lock, LOCK_UN); fclose($lock); } } private static function installErrorPolicy(): void { if (self::$errorPolicyInstalled) { return; } self::$errorPolicyInstalled = true; error_reporting(E_ALL); ini_set('display_errors', '0'); set_error_handler(static function ( int $severity, string $message, string $file = '', int $line = 0 ): bool { $handledSeverities = [ E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE, E_DEPRECATED, E_USER_DEPRECATED, ]; if (!in_array($severity, $handledSeverities, true)) { return false; } if ((error_reporting() & $severity) === 0) { return true; } $normalizedFile = str_replace('\\', '/', $file); if (str_contains($normalizedFile, '/vendor/')) { self::$vendorWarningsIgnored++; return true; } throw new RuntimeException(sprintf( 'First-party runtime warning (%s) in %s:%d: %s', self::severityName($severity), $file, $line, $message )); }); } private static function severityName(int $severity): string { return match ($severity) { E_WARNING => 'E_WARNING', E_NOTICE => 'E_NOTICE', E_USER_WARNING => 'E_USER_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_DEPRECATED => 'E_DEPRECATED', E_USER_DEPRECATED => 'E_USER_DEPRECATED', default => 'E_' . $severity, }; } }