From 898726afa5513b07ea8d1f66f785a989e849f32f Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Thu, 14 May 2026 01:18:29 +0300 Subject: [PATCH 1/2] Optimize cache storage layout for PSR-4/PSR-0 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the _proxies subdirectory and flatten the cache layout so proxy files reside at their PSR-4-compliant path (derived from the original file location) directly in the cache root. Transformed source files (containing __AopProxied traits) now use a filename that matches the entity name, enabling the cache directory to be registered as a Composer PSR-4/PSR-0 autoload root. Cache path formulas: - Proxy: / (standard, same as original) - Trait: /__AopProxied.php (suffix applied when transformed source contains AOP_PROXIED_SUFFIX) - Reverse: strip __AopProxied suffix, remap cacheDir→appDir Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Console/Command/DebugWeavingCommand.php | 26 ++++++++++++++----- .../ClassLoading/CachePathManager.php | 5 ++++ .../Transformer/CachingTransformer.php | 6 +++++ .../Transformer/MagicConstantTransformer.php | 15 +++++------ .../Transformer/WeavingTransformer.php | 12 ++++----- .../Transformer/WeavingTransformerTest.php | 2 +- .../_files/class-typehint-woven.php | 2 +- .../Transformer/_files/class-woven.php | 2 +- .../_files/final-readonly-class-woven.php | 2 +- .../_files/multiple-classes-woven.php | 6 ++--- .../Transformer/_files/multiple-ns-woven.php | 4 +-- .../Transformer/_files/php7-class-woven.php | 2 +- .../Transformer/_files/php81-enum-woven.php | 2 +- .../_files/php83-override-woven.php | 2 +- tests/PhpUnit/ClassIsNotWovenConstraint.php | 5 ++-- tests/PhpUnit/ClassWovenConstraint.php | 5 ++-- tests/PhpUnit/ProxyClassReflectionHelper.php | 16 +++++------- 17 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/Console/Command/DebugWeavingCommand.php b/src/Console/Command/DebugWeavingCommand.php index 5dffef51..af4025e0 100644 --- a/src/Console/Command/DebugWeavingCommand.php +++ b/src/Console/Command/DebugWeavingCommand.php @@ -13,6 +13,7 @@ namespace Go\Console\Command; use FilesystemIterator; +use Go\Core\AspectContainer; use Go\Instrument\ClassLoading\CachePathManager; use Go\Instrument\ClassLoading\CacheWarmer; use RecursiveDirectoryIterator; @@ -104,7 +105,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private function getProxies(CachePathManager $cachePathManager): array { - $path = $cachePathManager->getCacheDir() . '/_proxies'; + $path = $cachePathManager->getCacheDir(); + if ($path === null) { + return []; + } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path, @@ -119,11 +123,21 @@ private function getProxies(CachePathManager $cachePathManager): array * @var SplFileInfo $splFileInfo */ foreach ($iterator as $splFileInfo) { - if ($splFileInfo->isFile()) { - $content = file_get_contents($splFileInfo->getPathname()); - if ($content !== false) { - $proxies[$splFileInfo->getPathname()] = $content; - } + if (!$splFileInfo->isFile() || $splFileInfo->getExtension() !== 'php') { + continue; + } + $pathname = $splFileInfo->getPathname(); + if (str_contains($pathname, AspectContainer::AOP_PROXIED_SUFFIX)) { + continue; + } + // Only collect proxy files: they have a sibling __AopProxied trait file + $traitSibling = str_replace('.php', AspectContainer::AOP_PROXIED_SUFFIX . '.php', $pathname); + if (!file_exists($traitSibling)) { + continue; + } + $content = file_get_contents($pathname); + if ($content !== false) { + $proxies[$pathname] = $content; } } diff --git a/src/Instrument/ClassLoading/CachePathManager.php b/src/Instrument/ClassLoading/CachePathManager.php index 20f50293..8ed44ab3 100644 --- a/src/Instrument/ClassLoading/CachePathManager.php +++ b/src/Instrument/ClassLoading/CachePathManager.php @@ -122,6 +122,11 @@ public function getCachePathForResource(string $resource) return false; } + $cacheState = $this->queryCacheState($resource); + if ($cacheState !== null && isset($cacheState['cacheUri']) && is_string($cacheState['cacheUri'])) { + return $cacheState['cacheUri']; + } + return $this->appDir !== null ? str_replace($this->appDir, $this->cacheDir, $resource) : $resource; diff --git a/src/Instrument/Transformer/CachingTransformer.php b/src/Instrument/Transformer/CachingTransformer.php index 0990dff5..8a9f9b28 100644 --- a/src/Instrument/Transformer/CachingTransformer.php +++ b/src/Instrument/Transformer/CachingTransformer.php @@ -13,6 +13,7 @@ namespace Go\Instrument\Transformer; use Closure; +use Go\Core\AspectContainer; use Go\Core\AspectKernel; use Go\Instrument\ClassLoading\CachePathManager; use Go\ParserReflection\ReflectionEngine; @@ -77,6 +78,11 @@ public function transform(StreamMetaData $metadata): TransformerResultEnum ) { $processingResult = $this->processTransformers($metadata); if ($processingResult === TransformerResultEnum::RESULT_TRANSFORMED) { + if (!str_contains($cacheUri, AspectContainer::AOP_PROXIED_SUFFIX) + && str_contains($metadata->source, AspectContainer::AOP_PROXIED_SUFFIX) + ) { + $cacheUri = str_replace('.php', AspectContainer::AOP_PROXIED_SUFFIX . '.php', $cacheUri); + } $parentCacheDir = dirname($cacheUri); if (!is_dir($parentCacheDir)) { mkdir($parentCacheDir, $this->cacheFileMode, true); diff --git a/src/Instrument/Transformer/MagicConstantTransformer.php b/src/Instrument/Transformer/MagicConstantTransformer.php index a107a85e..a3ea7164 100644 --- a/src/Instrument/Transformer/MagicConstantTransformer.php +++ b/src/Instrument/Transformer/MagicConstantTransformer.php @@ -12,6 +12,7 @@ namespace Go\Instrument\Transformer; +use Go\Core\AspectContainer; use Go\Core\AspectKernel; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; @@ -66,14 +67,12 @@ public function transform(StreamMetaData $metadata): TransformerResultEnum */ public static function resolveFileName(string $fileName): string { - $suffix = '.php'; - $pathParts = explode($suffix, str_replace( - [self::$rewriteToPath, DIRECTORY_SEPARATOR . '_proxies'], - [self::$rootPath, ''], - $fileName - )); - // throw away namespaced path from actual filename - return $pathParts[0] . $suffix; + if (self::$rewriteToPath !== '' && str_starts_with($fileName, self::$rewriteToPath)) { + $fileName = str_replace(self::$rewriteToPath, self::$rootPath, $fileName); + $fileName = str_replace(AspectContainer::AOP_PROXIED_SUFFIX . '.php', '.php', $fileName); + } + + return $fileName; } /** diff --git a/src/Instrument/Transformer/WeavingTransformer.php b/src/Instrument/Transformer/WeavingTransformer.php index 6373c858..4454bcb3 100644 --- a/src/Instrument/Transformer/WeavingTransformer.php +++ b/src/Instrument/Transformer/WeavingTransformer.php @@ -40,7 +40,6 @@ class WeavingTransformer extends BaseSourceTransformer { private const FUNCTIONS_CACHE_SUFFIX = '/_functions/'; - private const PROXIES_CACHE_SUFFIX = '/_proxies/'; /** * Advice matcher for class @@ -708,18 +707,17 @@ private function processFunctions( */ private function saveProxyToCache(ReflectionClass $class, string $childCode): string { - $cacheRootDir = $this->cachePathManager->getCacheDir(); + $cacheRootDir = $this->cachePathManager->getCacheDir(); if ($cacheRootDir === null) { return ''; } - $cacheDir = $cacheRootDir . self::PROXIES_CACHE_SUFFIX; - $classFileName = $class->getFileName(); + $classFileName = $class->getFileName(); if ($classFileName === false) { return ''; } $relativePath = str_replace($this->options['appDir'] . DIRECTORY_SEPARATOR, '', $classFileName); - $proxyRelativePath = str_replace('\\', '/', $relativePath . '/' . $class->getName() . '.php'); - $proxyFileName = $cacheDir . $proxyRelativePath; + $proxyRelativePath = str_replace('\\', '/', $relativePath); + $proxyFileName = $cacheRootDir . '/' . $proxyRelativePath; $dirname = dirname($proxyFileName); if (!file_exists($dirname)) { mkdir($dirname, $this->options['cacheFileMode'], true); @@ -732,7 +730,7 @@ private function saveProxyToCache(ReflectionClass $class, string $childCode): st // For cache files we don't want executable bits by default chmod($proxyFileName, $this->options['cacheFileMode'] & (~0111)); - return 'include_once AOP_CACHE_DIR . ' . var_export(self::PROXIES_CACHE_SUFFIX . $proxyRelativePath, true) . ';'; + return 'include_once AOP_CACHE_DIR . ' . var_export('/' . $proxyRelativePath, true) . ';'; } /** diff --git a/tests/Instrument/Transformer/WeavingTransformerTest.php b/tests/Instrument/Transformer/WeavingTransformerTest.php index 15b63f2d..70e82702 100644 --- a/tests/Instrument/Transformer/WeavingTransformerTest.php +++ b/tests/Instrument/Transformer/WeavingTransformerTest.php @@ -128,7 +128,7 @@ public function testWeaverForTypeHint(): void $expected = $this->normalizeWhitespaces($this->loadTestMetadata('class-typehint-woven')->source); $this->assertEquals($expected, $actual); - $proxyContent = file_get_contents($this->cachePathManager->getCacheDir() . '_proxies/Transformer/_files/class-typehint.php/TestClassTypehint.php'); + $proxyContent = file_get_contents($this->cachePathManager->getCacheDir() . '/Transformer/_files/class-typehint.php'); $this->assertFalse(strpos($proxyContent, '\\\\Exception')); } diff --git a/tests/Instrument/Transformer/_files/class-typehint-woven.php b/tests/Instrument/Transformer/_files/class-typehint-woven.php index f84f6da0..501994df 100644 --- a/tests/Instrument/Transformer/_files/class-typehint-woven.php +++ b/tests/Instrument/Transformer/_files/class-typehint-woven.php @@ -5,4 +5,4 @@ trait TestClassTypehint__AopProxied { public function publicMethodFixedArguments(Exception $a, $b, $c = null) {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/class-typehint.php/TestClassTypehint.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/class-typehint.php'; diff --git a/tests/Instrument/Transformer/_files/class-woven.php b/tests/Instrument/Transformer/_files/class-woven.php index 7d0c5979..749d3523 100644 --- a/tests/Instrument/Transformer/_files/class-woven.php +++ b/tests/Instrument/Transformer/_files/class-woven.php @@ -22,4 +22,4 @@ public function publicMethodFixedArguments($a, $b, $c = null) {} public function methodWithSpecialTypeArguments(self $instance) {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/class.php/Test/ns1/TestClass.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/class.php'; diff --git a/tests/Instrument/Transformer/_files/final-readonly-class-woven.php b/tests/Instrument/Transformer/_files/final-readonly-class-woven.php index 5c4a93d6..06d4a974 100644 --- a/tests/Instrument/Transformer/_files/final-readonly-class-woven.php +++ b/tests/Instrument/Transformer/_files/final-readonly-class-woven.php @@ -19,4 +19,4 @@ public static function staticMethod(): string return static::class; } } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/final-readonly-class.php/Test/ns1/TestReadonlyClass.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/final-readonly-class.php'; diff --git a/tests/Instrument/Transformer/_files/multiple-classes-woven.php b/tests/Instrument/Transformer/_files/multiple-classes-woven.php index 9bbfc806..7e3436fb 100644 --- a/tests/Instrument/Transformer/_files/multiple-classes-woven.php +++ b/tests/Instrument/Transformer/_files/multiple-classes-woven.php @@ -5,15 +5,15 @@ trait TestClass1__AopProxied { public static function test() {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/multiple-classes.php/Test/ns3/TestClass1.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/multiple-classes.php'; TestClass1::test(); trait TestClass11__AopProxied { public static function test() {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/multiple-classes.php/Test/ns3/TestClass11.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/multiple-classes.php'; TestClass11::test(); trait TestClass2__AopProxied { public static function test() {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/multiple-classes.php/Test/ns3/TestClass2.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/multiple-classes.php'; TestClass2::test(); diff --git a/tests/Instrument/Transformer/_files/multiple-ns-woven.php b/tests/Instrument/Transformer/_files/multiple-ns-woven.php index ce35abd7..1ce72e95 100644 --- a/tests/Instrument/Transformer/_files/multiple-ns-woven.php +++ b/tests/Instrument/Transformer/_files/multiple-ns-woven.php @@ -4,11 +4,11 @@ trait TestClass1__AopProxied { public static function test() {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/multiple-ns.php/Test/ns1/TestClass1.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/multiple-ns.php'; } namespace Test\ns2 { trait TestClass2__AopProxied { public static function test() {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/multiple-ns.php/Test/ns2/TestClass2.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/multiple-ns.php'; } diff --git a/tests/Instrument/Transformer/_files/php7-class-woven.php b/tests/Instrument/Transformer/_files/php7-class-woven.php index 3834b448..3cbb31db 100644 --- a/tests/Instrument/Transformer/_files/php7-class-woven.php +++ b/tests/Instrument/Transformer/_files/php7-class-woven.php @@ -21,4 +21,4 @@ public function exceptionRth(\Exception $exception) : \Exception {} public function noRth(LocalException $exception) {} public function returnSelf(): self {} } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/php7-class.php/Test/ns1/TestPhp7Class.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/php7-class.php'; diff --git a/tests/Instrument/Transformer/_files/php81-enum-woven.php b/tests/Instrument/Transformer/_files/php81-enum-woven.php index 1789184b..ca6d98cd 100644 --- a/tests/Instrument/Transformer/_files/php81-enum-woven.php +++ b/tests/Instrument/Transformer/_files/php81-enum-woven.php @@ -18,4 +18,4 @@ public function label(): string }; } } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/php81-enum.php/Test/ns1/TestStatus.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/php81-enum.php'; diff --git a/tests/Instrument/Transformer/_files/php83-override-woven.php b/tests/Instrument/Transformer/_files/php83-override-woven.php index d02d7980..9f0af489 100644 --- a/tests/Instrument/Transformer/_files/php83-override-woven.php +++ b/tests/Instrument/Transformer/_files/php83-override-woven.php @@ -19,4 +19,4 @@ public function normalMethod(): int return 42; } } -include_once AOP_CACHE_DIR . '/_proxies/Transformer/_files/php83-override.php/Test/ns1/TestClassWithOverride.php'; +include_once AOP_CACHE_DIR . '/Transformer/_files/php83-override.php'; diff --git a/tests/PhpUnit/ClassIsNotWovenConstraint.php b/tests/PhpUnit/ClassIsNotWovenConstraint.php index 4bb5bad5..e6e6a012 100644 --- a/tests/PhpUnit/ClassIsNotWovenConstraint.php +++ b/tests/PhpUnit/ClassIsNotWovenConstraint.php @@ -12,6 +12,7 @@ namespace Go\PhpUnit; +use Go\Core\AspectContainer; use Go\Instrument\PathResolver; use Go\ParserReflection\ReflectionClass; use PHPUnit\Framework\Constraint\Constraint; @@ -36,8 +37,8 @@ public function matches($other): bool $filename = (new ReflectionClass($other))->getFileName(); $suffix = substr($filename, strlen(PathResolver::realpath($this->configuration['appDir']))); - $transformedFileExists = file_exists($this->configuration['cacheDir'] . $suffix); - $proxyFileExists = file_exists($this->configuration['cacheDir'] . '/_proxies' . $suffix); + $proxyFileExists = file_exists($this->configuration['cacheDir'] . $suffix); + $transformedFileExists = file_exists($this->configuration['cacheDir'] . str_replace('.php', AspectContainer::AOP_PROXIED_SUFFIX . '.php', $suffix)); // if any of files exists, assert has to fail return !$transformedFileExists && !$proxyFileExists; diff --git a/tests/PhpUnit/ClassWovenConstraint.php b/tests/PhpUnit/ClassWovenConstraint.php index 6c393c98..a9b9df6d 100644 --- a/tests/PhpUnit/ClassWovenConstraint.php +++ b/tests/PhpUnit/ClassWovenConstraint.php @@ -12,6 +12,7 @@ namespace Go\PhpUnit; +use Go\Core\AspectContainer; use Go\Instrument\PathResolver; use Go\ParserReflection\ReflectionClass; use PHPUnit\Framework\Constraint\Constraint; @@ -36,8 +37,8 @@ public function matches($other): bool $filename = (new ReflectionClass($other))->getFileName(); $suffix = substr($filename, strlen(PathResolver::realpath($this->configuration['appDir']))); - $transformedFileExists = file_exists($this->configuration['cacheDir'] . $suffix); - $proxyFileExists = file_exists($this->configuration['cacheDir'] . '/_proxies' . $suffix); + $proxyFileExists = file_exists($this->configuration['cacheDir'] . $suffix); + $transformedFileExists = file_exists($this->configuration['cacheDir'] . str_replace('.php', AspectContainer::AOP_PROXIED_SUFFIX . '.php', $suffix)); // if any of files is missing, assert has to fail return $transformedFileExists && $proxyFileExists; diff --git a/tests/PhpUnit/ProxyClassReflectionHelper.php b/tests/PhpUnit/ProxyClassReflectionHelper.php index 89b74440..9d93cc29 100644 --- a/tests/PhpUnit/ProxyClassReflectionHelper.php +++ b/tests/PhpUnit/ProxyClassReflectionHelper.php @@ -48,11 +48,9 @@ public static function extractAdvicesFromProxyFile(string $className, array $con $parsedReflectionClass = new ReflectionClass($className); $originalClassFile = $parsedReflectionClass->getFileName(); - $appDir = PathResolver::realpath($configuration['appDir']); - $relativePath = str_replace($appDir . DIRECTORY_SEPARATOR, '', $originalClassFile); - $classSuffix = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php'; - $proxyRelativePath = $relativePath . DIRECTORY_SEPARATOR . $classSuffix; - $proxyFileName = $configuration['cacheDir'] . '/_proxies/' . $proxyRelativePath; + $appDir = PathResolver::realpath($configuration['appDir']); + $relativePath = str_replace($appDir . DIRECTORY_SEPARATOR, '', $originalClassFile); + $proxyFileName = $configuration['cacheDir'] . '/' . str_replace('\\', '/', $relativePath); if (!file_exists($proxyFileName)) { return []; @@ -212,11 +210,9 @@ public static function createReflectionClass(string $className, array $configura $originalClassFile = $parsedReflectionClass->getFileName(); $originalNamespace = $parsedReflectionClass->getNamespaceName(); - $appDir = PathResolver::realpath($configuration['appDir']); - $relativePath = str_replace($appDir . DIRECTORY_SEPARATOR, '', $originalClassFile); - $classSuffix = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php'; - $proxyRelativePath = $relativePath . DIRECTORY_SEPARATOR . $classSuffix; - $proxyFileName = $configuration['cacheDir'] . '/_proxies/' . $proxyRelativePath; + $appDir = PathResolver::realpath($configuration['appDir']); + $relativePath = str_replace($appDir . DIRECTORY_SEPARATOR, '', $originalClassFile); + $proxyFileName = $configuration['cacheDir'] . '/' . str_replace('\\', '/', $relativePath); $proxyFileContent = file_get_contents($proxyFileName); // To prevent deep analysis of parents, we just cut everything after "extends" From beb0f7e3d164985fca2918cf7ab99d0655dcfcaf Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Thu, 14 May 2026 01:45:11 +0300 Subject: [PATCH 2/2] chore: update AGENTS.md to remove _proxies as well --- src/Instrument/AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrument/AGENTS.md b/src/Instrument/AGENTS.md index f90b903d..ae94f90e 100644 --- a/src/Instrument/AGENTS.md +++ b/src/Instrument/AGENTS.md @@ -20,7 +20,7 @@ WeavingTransformer converts original class to trait + proxy class. Two generated ### Woven file (replaces original in php stream filtering) ```php trait Foo__AopProxied { /* original methods verbatim */ } -include_once AOP_CACHE_DIR . '/_proxies/.../Foo.php'; +include_once AOP_CACHE_DIR . '/Foo.php'; ``` ### Proxy file (loaded by include_once)