diff --git a/src/Instrument/ClassLoading/CachePathManager.php b/src/Instrument/ClassLoading/CachePathManager.php index c437c368..4d72bc13 100644 --- a/src/Instrument/ClassLoading/CachePathManager.php +++ b/src/Instrument/ClassLoading/CachePathManager.php @@ -148,6 +148,11 @@ public function __construct(protected readonly AspectKernel $kernel) $this->cacheStateLoaded = true; } } + + // Flush pending cache records while the runtime environment is still fully + // intact: object destruction order during shutdown is unspecified, so relying + // on __destruct() alone can run the write after collaborators are torn down + register_shutdown_function($this->flushSilently(...)); } /** @@ -302,11 +307,28 @@ public function setCacheState(string $resource, array $metadata): void /** * Automatic destructor saves all new changes into the cache * + * Safety net for managers released before shutdown; the shutdown function + * registered in the constructor has usually flushed already, making this a no-op. * This implementation is not thread-safe, so be care */ public function __destruct() { - $this->flushCacheState(); + $this->flushSilently(); + } + + /** + * Flushes without ever propagating an error out of shutdown/destruction + * + * Losing one cache write is recoverable (the next request simply re-weaves); + * an exception escaping a destructor or shutdown function is not. + */ + private function flushSilently(): void + { + try { + $this->flushCacheState(); + } catch (\Throwable) { + // Deliberately swallowed, see above + } } /** diff --git a/src/Instrument/Transformer/FilterInjectorTransformer.php b/src/Instrument/Transformer/FilterInjectorTransformer.php index a74e215a..d193fff8 100644 --- a/src/Instrument/Transformer/FilterInjectorTransformer.php +++ b/src/Instrument/Transformer/FilterInjectorTransformer.php @@ -108,10 +108,8 @@ public static function rewrite(string $originalResource, string $originalDir = ' { self::ensureConfigured(); - static $appDir, $cacheDir, $debug; - if ($appDir === null) { - extract(self::$options, EXTR_IF_EXISTS); - } + $cacheDir = self::$options['cacheDir']; + $debug = self::$options['debug']; $resource = $originalResource; if ($resource[0] !== '/') { diff --git a/src/Instrument/Transformer/MagicConstantTransformer.php b/src/Instrument/Transformer/MagicConstantTransformer.php index 2c609274..b9ee4abf 100644 --- a/src/Instrument/Transformer/MagicConstantTransformer.php +++ b/src/Instrument/Transformer/MagicConstantTransformer.php @@ -27,6 +27,8 @@ * Transformer that replaces magic __DIR__ and __FILE__ constants in the source code * * Additionally, ReflectionClass->getFileName() is also wrapped into normalizer method call + * + * @phpstan-import-type KernelOptions from AspectKernel */ class MagicConstantTransformer extends BaseSourceTransformer { @@ -46,8 +48,18 @@ class MagicConstantTransformer extends BaseSourceTransformer public function __construct(AspectKernel $kernel) { parent::__construct($kernel); - self::$rootPath = $this->options['appDir']; - self::$rewriteToPath = $this->options['cacheDir'] ?? ''; + self::configurePaths($this->options); + } + + /** + * Remembers the path mapping used by resolveFileName() + * + * @phpstan-param KernelOptions $options + */ + private static function configurePaths(array $options): void + { + self::$rootPath = $options['appDir']; + self::$rewriteToPath = $options['cacheDir'] ?? ''; } /** @@ -71,9 +83,7 @@ public function transform(StreamMetaData $metadata): TransformerResultEnum public static function resolveFileName(string $fileName): string { if (self::$rootPath === '') { - $options = AspectKernel::getInstance()->getOptions(); - self::$rootPath = $options['appDir']; - self::$rewriteToPath = $options['cacheDir'] ?? ''; + self::configurePaths(AspectKernel::getInstance()->getOptions()); } if (self::$rewriteToPath !== '' && str_starts_with($fileName, self::$rewriteToPath)) { $fileName = str_replace(self::$rewriteToPath, self::$rootPath, $fileName);