From f5e90ffdf235fef5f5cb09ebe9c237e73c04cbf3 Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Sun, 14 Apr 2024 12:12:56 +0300 Subject: [PATCH] [BC Break] Internal container refactoring. New version drops GoAspectContainer class and rely just on Container class now. Container uses now generic methods, so IDE and PhpStan aware about types now. Tags have been replaced with interface names, which simplifies usage of marker interfaces. All unnecessary string variables were replaced with class FQN. No more special methods to register advisors or pointcuts - they are just values in container. --- src/Aop/Framework/AbstractInterceptor.php | 5 +- src/Aop/Pointcut/PointcutReference.php | 7 +- src/Aop/Support/LazyPointcutAdvisor.php | 10 +- src/Aop/Support/PointcutBuilder.php | 6 +- src/Console/Command/BaseAspectCommand.php | 2 +- src/Console/Command/DebugAdvisorCommand.php | 22 +- src/Console/Command/DebugAspectCommand.php | 9 +- src/Console/Command/DebugWeavingCommand.php | 2 +- src/Core/AspectContainer.php | 75 +++---- src/Core/AspectKernel.php | 67 +++--- src/Core/AspectLoader.php | 52 ++--- src/Core/AttributeAspectLoaderExtension.php | 7 +- src/Core/CachedAspectLoader.php | 12 +- src/Core/Container.php | 195 +++++++++++++----- src/Core/GoAspectContainer.php | 175 ---------------- src/Core/LazyAdvisorAccessor.php | 13 +- .../ClassLoading/AopComposerLoader.php | 2 +- .../Transformer/CachingTransformer.php | 2 +- .../Transformer/WeavingTransformer.php | 4 +- src/Proxy/ClassProxyGenerator.php | 3 +- src/Proxy/FunctionProxyGenerator.php | 3 +- src/Proxy/TraitProxyGenerator.php | 3 +- tests/Go/Core/ContainerTest.php | 164 +++++++++++++++ tests/Go/Core/GoAspectContainerTest.php | 111 ---------- tests/Go/Functional/ClassWeavingTest.php | 16 +- tests/Go/Functional/Issue293Test.php | 4 +- .../FilterInjectorTransformerTest.php | 7 +- .../Transformer/WeavingTransformerTest.php | 9 +- 28 files changed, 463 insertions(+), 524 deletions(-) delete mode 100644 src/Core/GoAspectContainer.php create mode 100644 tests/Go/Core/ContainerTest.php delete mode 100644 tests/Go/Core/GoAspectContainerTest.php diff --git a/src/Aop/Framework/AbstractInterceptor.php b/src/Aop/Framework/AbstractInterceptor.php index 5600874d..e12327cd 100644 --- a/src/Aop/Framework/AbstractInterceptor.php +++ b/src/Aop/Framework/AbstractInterceptor.php @@ -13,6 +13,7 @@ namespace Go\Aop\Framework; use Closure; +use Go\Aop\Aspect; use Go\Aop\AspectException; use Go\Aop\Intercept\Interceptor; use Go\Aop\OrderedAdvice; @@ -89,7 +90,7 @@ public static function serializeAdvice(Closure $adviceMethod): array public static function unserializeAdvice(array $adviceData): Closure { // General unpacking supports only aspect's advices - if (!isset($adviceData['class'])) { + if (!isset($adviceData['class']) || !is_subclass_of($adviceData['class'], Aspect::class)) { throw new AspectException('Could not unpack an interceptor without aspect name'); } $aspectName = $adviceData['class']; @@ -97,7 +98,7 @@ public static function unserializeAdvice(array $adviceData): Closure // With aspect name and method name, we can restore back a closure for it if (!isset(self::$localAdvicesCache["$aspectName->$methodName"])) { - $aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName); + $aspect = AspectKernel::getInstance()->getContainer()->getService($aspectName); $advice = (new ReflectionMethod($aspectName, $methodName))->getClosure($aspect); assert(isset($advice), 'getClosure() can not be null on modern PHP versions'); diff --git a/src/Aop/Pointcut/PointcutReference.php b/src/Aop/Pointcut/PointcutReference.php index 32c22a72..34fd5675 100644 --- a/src/Aop/Pointcut/PointcutReference.php +++ b/src/Aop/Pointcut/PointcutReference.php @@ -12,6 +12,7 @@ namespace Go\Aop\Pointcut; +use Go\Aop\AspectException; use Go\Aop\Pointcut; use Go\Core\AspectContainer; use Go\Core\AspectKernel; @@ -68,7 +69,11 @@ public function __wakeup(): void private function getPointcut(): Pointcut { if (!isset($this->pointcut)) { - $this->pointcut = $this->container->getPointcut($this->pointcutId); + $pointcutValue = $this->container->getValue($this->pointcutId); + if (!$pointcutValue instanceof Pointcut) { + throw new AspectException("Reference {$this->pointcutId} points not to a Pointcut."); + } + $this->pointcut = $pointcutValue; } return $this->pointcut; diff --git a/src/Aop/Support/LazyPointcutAdvisor.php b/src/Aop/Support/LazyPointcutAdvisor.php index a9da8666..a3a79c69 100644 --- a/src/Aop/Support/LazyPointcutAdvisor.php +++ b/src/Aop/Support/LazyPointcutAdvisor.php @@ -14,6 +14,8 @@ use Go\Aop\Advice; use Go\Aop\Pointcut; +use Go\Aop\Pointcut\PointcutLexer; +use Go\Aop\Pointcut\PointcutParser; use Go\Aop\PointcutAdvisor; use Go\Core\AspectContainer; @@ -42,12 +44,8 @@ public function getPointcut(): Pointcut { if (!isset($this->pointcut)) { // Inject these dependencies and make them lazy! - - /** @var Pointcut\PointcutLexer $lexer */ - $lexer = $this->container->get('aspect.pointcut.lexer'); - - /** @var Pointcut\PointcutParser $parser */ - $parser = $this->container->get('aspect.pointcut.parser'); + $lexer = $this->container->getService(PointcutLexer::class); + $parser = $this->container->getService(PointcutParser::class); $tokenStream = $lexer->lex($this->pointcutExpression); $this->pointcut = $parser->parse($tokenStream); diff --git a/src/Aop/Support/PointcutBuilder.php b/src/Aop/Support/PointcutBuilder.php index d31f800b..9f584c00 100644 --- a/src/Aop/Support/PointcutBuilder.php +++ b/src/Aop/Support/PointcutBuilder.php @@ -84,9 +84,9 @@ public function declareError(string $pointcutExpression, string $message, int $e */ private function registerAdviceInContainer(string $pointcutExpression, Advice $adviceToInvoke): void { - $this->container->registerAdvisor( - new LazyPointcutAdvisor($this->container, $pointcutExpression, $adviceToInvoke), - $this->getPointcutId($pointcutExpression) + $this->container->add( + $this->getPointcutId($pointcutExpression), + new LazyPointcutAdvisor($this->container, $pointcutExpression, $adviceToInvoke) ); } diff --git a/src/Console/Command/BaseAspectCommand.php b/src/Console/Command/BaseAspectCommand.php index 82983f63..3d1f04e7 100644 --- a/src/Console/Command/BaseAspectCommand.php +++ b/src/Console/Command/BaseAspectCommand.php @@ -29,7 +29,7 @@ class BaseAspectCommand extends Command /** * Stores an instance of aspect kernel */ - protected ?AspectKernel $aspectKernel = null; + protected AspectKernel $aspectKernel; /** * {@inheritDoc} diff --git a/src/Console/Command/DebugAdvisorCommand.php b/src/Console/Command/DebugAdvisorCommand.php index 1f0749a9..e2ce62fd 100644 --- a/src/Console/Command/DebugAdvisorCommand.php +++ b/src/Console/Command/DebugAdvisorCommand.php @@ -14,9 +14,8 @@ use Go\Aop\Advisor; use Go\Core\AdviceMatcher; -use Go\Core\AdviceMatcherInterface; use Go\Core\AspectContainer; -use Go\Core\AspectLoader; +use Go\Core\CachedAspectLoader; use Go\Instrument\FileSystem\Enumerator; use Go\ParserReflection\ReflectionFile; use ReflectionClass; @@ -63,9 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->title('Advisor debug information'); $advisorId = $input->getOption('advisor'); - if (!$advisorId) { + if (empty($advisorId)) { $this->showAdvisorsList($io); } else { + assert(is_string($advisorId), "Option 'advisor' must be a string, " . gettype($advisorId) . " given"); $this->showAdvisorInformation($io, $advisorId); } @@ -81,7 +81,6 @@ private function showAdvisorsList(SymfonyStyle $io): void $tableRows = []; foreach ($advisors as $id => $advisor) { - [, $id] = explode('.', $id, 2); $advice = $advisor->getAdvice(); $expression = ''; try { @@ -106,11 +105,13 @@ private function showAdvisorInformation(SymfonyStyle $io, string $advisorId): vo { $aspectContainer = $this->aspectKernel->getContainer(); - /** @var AdviceMatcherInterface $adviceMatcher */ - $adviceMatcher = $aspectContainer->get('aspect.advice_matcher'); + $adviceMatcher = $aspectContainer->getService(AdviceMatcher::class); $this->loadAdvisorsList($aspectContainer); - $advisor = $aspectContainer->getAdvisor($advisorId); + $advisor = $aspectContainer->getValue($advisorId); + if (!$advisor instanceof Advisor) { + throw new \InvalidArgumentException("Invalid advisor {$advisorId} given"); + } $options = $this->aspectKernel->getOptions(); $enumerator = new Enumerator($options['appDir'], $options['includePaths'], $options['excludePaths']); @@ -151,14 +152,11 @@ private function writeInfoAboutAdvices(SymfonyStyle $io, ReflectionClass $reflec */ private function loadAdvisorsList(AspectContainer $aspectContainer): array { - /** @var AspectLoader $aspectLoader */ - $aspectLoader = $aspectContainer->get('aspect.cached.loader'); + $aspectLoader = $aspectContainer->getService(CachedAspectLoader::class); $aspects = $aspectLoader->getUnloadedAspects(); foreach ($aspects as $aspect) { $aspectLoader->loadAndRegister($aspect); } - $advisors = $aspectContainer->getByTag('advisor'); - - return $advisors; + return $aspectContainer->getServicesByInterface(Advisor::class); } } diff --git a/src/Console/Command/DebugAspectCommand.php b/src/Console/Command/DebugAspectCommand.php index 2130e1ce..572c06b7 100644 --- a/src/Console/Command/DebugAspectCommand.php +++ b/src/Console/Command/DebugAspectCommand.php @@ -61,9 +61,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $aspectName = $input->getOption('aspect'); if (!$aspectName) { $io->text('' . get_class($this->aspectKernel) . ' has following enabled aspects:'); - $aspects = $container->getByTag('aspect'); - } else { - $aspect = $container->getAspect($aspectName); + $aspects = $container->getServicesByInterface(Aspect::class); + } elseif (is_string($aspectName) && is_subclass_of($aspectName, Aspect::class)) { + $aspect = $container->getService($aspectName); $aspects[] = $aspect; } $this->showRegisteredAspectsInfo($io, $aspects); @@ -106,8 +106,7 @@ private function showAspectPointcutsAndAdvisors(SymfonyStyle $io, Aspect $aspect { $container = $this->aspectKernel->getContainer(); - /** @var AspectLoader $aspectLoader */ - $aspectLoader = $container->get('aspect.loader'); + $aspectLoader = $container->getService(AspectLoader::class); $io->writeln('Pointcuts and advices'); $aspectItems = $aspectLoader->load($aspect); diff --git a/src/Console/Command/DebugWeavingCommand.php b/src/Console/Command/DebugWeavingCommand.php index f166ebc3..da7eb4cc 100644 --- a/src/Console/Command/DebugWeavingCommand.php +++ b/src/Console/Command/DebugWeavingCommand.php @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->title('Weaving debug information'); - $cachePathManager = $this->aspectKernel->getContainer()->get('aspect.cache.path.manager'); + $cachePathManager = $this->aspectKernel->getContainer()->getService(CachePathManager::class); $warmer = new CacheWarmer($this->aspectKernel, new NullOutput()); $warmer->warmUp(); diff --git a/src/Core/AspectContainer.php b/src/Core/AspectContainer.php index 4ccd0411..9848590f 100644 --- a/src/Core/AspectContainer.php +++ b/src/Core/AspectContainer.php @@ -13,9 +13,7 @@ namespace Go\Core; use OutOfBoundsException; -use Go\Aop\Advisor; use Go\Aop\Aspect; -use Go\Aop\Pointcut; /** * Aspect container interface @@ -68,77 +66,60 @@ interface AspectContainer public const AOP_PROXIED_SUFFIX = '__AopProxied'; /** - * Return a service or value from the container + * Returns a service from the container. + * + * Supports lazy-initialization if value is defined as a closure, it will be invoked once to perform initialization. + * + * @param class-string $className Class-name of service to retrieve from the container + * @return object&T + * + * @template T of object * - * @return mixed * @throws OutOfBoundsException if service was not found */ - public function get(string $id); + public function getService(string $className): object; /** - * Return list of service tagged with marker + * Return list of services tagged with marker interface + * + * @param class-string $interfaceTagClassName Interface name of services to retrieve from the container + * @return T[] + * + * @template T */ - public function getByTag(string $tag): array; + public function getServicesByInterface(string $interfaceTagClassName): array; /** - * Returns a pointcut by identifier + * Returns a value from the container + * + * @param string $key Given key + * + * @return mixed + * @throws OutOfBoundsException if key was not found */ - public function getPointcut(string $id): Pointcut; + public function getValue(string $key): mixed; /** * Checks if item with specified id is present in the container */ public function has(string $id): bool; - /** - * Store the pointcut in the container - */ - public function registerPointcut(Pointcut $pointcut, string $id): void; - - /** - * Returns an advisor by identifier - */ - public function getAdvisor(string $id): Advisor; - - /** - * Store the advisor in the container - */ - public function registerAdvisor(Advisor $advisor, string $id): void; - /** * Register an aspect in the container */ public function registerAspect(Aspect $aspect): void; /** - * Returns an aspect by id or class name - */ - public function getAspect(string $aspectName): Aspect; - - /** - * Add an AOP resource to the container - * Resources is used to check the freshness of AOP cache - * - * @param string $resource Path to the resource - */ - public function addResource(string $resource); - - /** - * Returns the list of AOP resources - */ - public function getResources(): array; - - /** - * Checks the freshness of AOP cache + * Checks if there are any file resources with changes after since given timestamp * - * @return bool Whether or not concrete file is fresh + * @return bool Whether or not there are new changes (filemtime of any resource is greater than given) */ - public function isFresh(int $timestamp): bool; + public function hasAnyResourceChangedSince(int $timestamp): bool; /** - * Set a service into the container + * Adds a new item into the container * * @param mixed $value Value to store */ - public function set(string $id, $value, array $tags = []): void; + public function add(string $id, mixed $value): void; } diff --git a/src/Core/AspectKernel.php b/src/Core/AspectKernel.php index e3f092af..7f6119de 100644 --- a/src/Core/AspectKernel.php +++ b/src/Core/AspectKernel.php @@ -12,8 +12,10 @@ namespace Go\Core; +use Go\Aop\AspectException; use Go\Aop\Features; use Go\Instrument\ClassLoading\AopComposerLoader; +use Go\Instrument\ClassLoading\CachePathManager; use Go\Instrument\ClassLoading\SourceTransformingLoader; use Go\Instrument\PathResolver; use Go\Instrument\Transformer\CachingTransformer; @@ -23,7 +25,6 @@ use Go\Instrument\Transformer\SelfValueTransformer; use Go\Instrument\Transformer\SourceTransformer; use Go\Instrument\Transformer\WeavingTransformer; -use ReflectionObject; use RuntimeException; use function define; @@ -47,8 +48,9 @@ abstract class AspectKernel /** * Default class name for container, can be redefined in children + * @var class-string */ - protected static string $containerClass = GoAspectContainer::class; + protected static string $containerClass = Container::class; /** * Flag to determine if kernel was already initialized or not @@ -81,7 +83,16 @@ public static function getInstance(): self /** * Init the kernel and make adjustments * - * @param array $options Associative array of options for kernel + * @param array{ + * debug?: bool, + * appDir?: literal-string&non-falsy-string, + * cacheDir?: string|null, + * cacheFileMode?: int, + * features?: int, + * includePaths?: array{}, + * excludePaths?: array{}, + * containerClass?: class-string + * } $options Additional kernel options */ public function init(array $options = []): void { @@ -93,11 +104,19 @@ public function init(array $options = []): void define('AOP_ROOT_DIR', $this->options['appDir']); define('AOP_CACHE_DIR', $this->options['cacheDir']); - /** @var AspectContainer $container */ - $container = $this->container = new $this->options['containerClass']; - $container->set('kernel', $this); - $container->set('kernel.interceptFunctions', $this->hasFeature(Features::INTERCEPT_FUNCTIONS)); - $container->set('kernel.options', $this->options); + $resourcesToTrack = []; + if ($this->options['debug']) { + $resourcesToTrack[] = $this->getFileNameWhereInitialized(); + } + + if (!is_subclass_of($this->options['containerClass'], AspectContainer::class)) { + throw new AspectException("Invalid aspect container class"); + } + + $container = $this->container = new $this->options['containerClass']($resourcesToTrack); + $container->add(AspectKernel::class, $this); + $container->add('kernel.interceptFunctions', $this->hasFeature(Features::INTERCEPT_FUNCTIONS)); + $container->add('kernel.options', $this->options); SourceTransformingLoader::register(); @@ -105,11 +124,6 @@ public function init(array $options = []): void SourceTransformingLoader::addTransformer($sourceTransformer); } - // Register kernel resources in the container for debug mode - if ($this->options['debug']) { - $this->addKernelResourcesToContainer($container); - } - AopComposerLoader::init($this->options, $container); // Register all AOP configuration in the container @@ -151,7 +165,6 @@ public function getOptions(): array * appDir - string Path to the application root directory. * cacheDir - string Path to the cache directory where compiled classes will be stored * cacheFileMode - integer Binary mask of permission bits that is set to cache files - * annotationCache - Doctrine\Common\Cache\Cache. If not provided, Doctrine\Common\Cache\PhpFileCache is used. * features - integer Binary mask of features * includePaths - array Whitelist of directories where aspects should be applied. Empty for everywhere. * excludePaths - array Blacklist of directories or files where aspects shouldn't be applied. @@ -164,7 +177,6 @@ protected function getDefaultOptions(): array 'cacheDir' => null, 'cacheFileMode' => 0770 & ~umask(), // Respect user umask() policy 'features' => 0, - 'annotationCache' => null, 'includePaths' => [], 'excludePaths' => [], 'containerClass' => static::$containerClass, @@ -179,14 +191,12 @@ protected function getDefaultOptions(): array */ protected function normalizeOptions(array $options): array { - $options = array_replace($this->getDefaultOptions(), $options); + $options = [...$this->getDefaultOptions(), ...$options]; - $options['cacheDir'] = PathResolver::realpath($options['cacheDir']); - - if ($options['cacheDir'] === []) { + if (empty($options['cacheDir'])) { throw new RuntimeException('You need to provide valid cache directory for Go! AOP framework.'); } - + $options['cacheDir'] = PathResolver::realpath($options['cacheDir']); $options['excludePaths'][] = $options['cacheDir']; $options['excludePaths'][] = __DIR__ . '/../'; $options['appDir'] = PathResolver::realpath($options['appDir']); @@ -210,7 +220,7 @@ abstract protected function configureAop(AspectContainer $container); */ protected function registerTransformers(): array { - $cacheManager = $this->getContainer()->get('aspect.cache.path.manager'); + $cacheManager = $this->getContainer()->getService(CachePathManager::class); $filterInjector = new FilterInjectorTransformer($this, SourceTransformingLoader::getId(), $cacheManager); $magicTransformer = new MagicConstantTransformer($this); @@ -225,9 +235,9 @@ protected function registerTransformers(): array $transformers[] = new SelfValueTransformer($this); $transformers[] = new WeavingTransformer( $this, - $this->container->get('aspect.advice_matcher'), + $this->container->getService(AdviceMatcher::class), $cacheManager, - $this->container->get('aspect.cached.loader') + $this->container->getService(CachedAspectLoader::class) ); $transformers[] = $magicTransformer; @@ -240,14 +250,13 @@ protected function registerTransformers(): array } /** - * Add resources of kernel to the container + * Returns a file name where kernel has been initialized */ - protected function addKernelResourcesToContainer(AspectContainer $container): void + final protected function getFileNameWhereInitialized(): string { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); - $refClass = new ReflectionObject($this); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + assert(isset($trace[1]['file']), "There should be at least 2 stack frames here"); - $container->addResource($trace[1]['file']); - $container->addResource($refClass->getFileName()); + return $trace[1]['file']; } } diff --git a/src/Core/AspectLoader.php b/src/Core/AspectLoader.php index 8b5aa0f0..16f46334 100644 --- a/src/Core/AspectLoader.php +++ b/src/Core/AspectLoader.php @@ -17,48 +17,29 @@ use Go\Aop\Pointcut; use ReflectionClass; -use function get_class; - /** * Loader of aspects into the container */ class AspectLoader { /** - * Aspect container instance - */ - protected AspectContainer $container; - - /** - * List of aspect loaders - * - * @var AspectLoaderExtension[] + * @var AspectLoaderExtension[] List of aspect loaders */ - protected array $loaders = []; + protected readonly array $aspectLoaders; /** - * List of aspect class names that have been loaded - * - * @var string[] + * @var class-string[] List of aspect class names that have been loaded */ protected array $loadedAspects = []; /** * Loader constructor */ - public function __construct(AspectContainer $container) - { - $this->container = $container; - } - - /** - * Register an aspect loader extension - * - * This method allows to extend the logic of aspect loading by registering an extension for loader. - */ - public function registerLoaderExtension(AspectLoaderExtension $loader): void - { - $this->loaders[] = $loader; + public function __construct( + protected AspectContainer $container, + AspectLoaderExtension ...$aspectLoaders, + ) { + $this->aspectLoaders = $aspectLoaders; } /** @@ -73,7 +54,7 @@ public function load(Aspect $aspect): array $refAspect = new ReflectionClass($aspect); $loadedItems = []; - foreach ($this->loaders as $loader) { + foreach ($this->aspectLoaders as $loader) { $loadedItems += $loader->load($aspect, $refAspect); } @@ -87,16 +68,9 @@ public function loadAndRegister(Aspect $aspect): void { $loadedItems = $this->load($aspect); foreach ($loadedItems as $itemId => $item) { - if ($item instanceof Pointcut) { - $this->container->registerPointcut($item, $itemId); - } - if ($item instanceof Advisor) { - $this->container->registerAdvisor($item, $itemId); - } + $this->container->add($itemId, $item); } - $aspectClass = get_class($aspect); - - $this->loadedAspects[$aspectClass] = $aspectClass; + $this->loadedAspects[$aspect::class] = $aspect::class; } /** @@ -108,8 +82,8 @@ public function getUnloadedAspects(): array { $unloadedAspects = []; - foreach ($this->container->getByTag('aspect') as $aspect) { - if (!isset($this->loadedAspects[get_class($aspect)])) { + foreach ($this->container->getServicesByInterface(Aspect::class) as $aspect) { + if (!isset($this->loadedAspects[$aspect::class])) { $unloadedAspects[] = $aspect; } } diff --git a/src/Core/AttributeAspectLoaderExtension.php b/src/Core/AttributeAspectLoaderExtension.php index 090037e7..c0b7395f 100644 --- a/src/Core/AttributeAspectLoaderExtension.php +++ b/src/Core/AttributeAspectLoaderExtension.php @@ -13,7 +13,6 @@ namespace Go\Core; use Closure; -use Go\Aop\Advisor; use Go\Aop\Aspect; use Go\Aop\Framework\AfterInterceptor; use Go\Aop\Framework\AfterThrowingInterceptor; @@ -31,8 +30,6 @@ use ReflectionMethod; use UnexpectedValueException; -use function get_class; - /** * Attribute aspect loader add common support for general advices, declared as attributes */ @@ -55,7 +52,7 @@ public function load(Aspect $aspect, ReflectionClass $reflectionAspect): array $loadedItems[$methodId] = new GenericPointcutAdvisor($pointcut, $interceptor); } else { - throw new UnexpectedValueException('Unsupported attribute class: ' . get_class($attribute)); + throw new UnexpectedValueException('Unsupported attribute class: ' . $attribute::class); } } } @@ -83,7 +80,7 @@ protected function getAdvice( $interceptorAttribute instanceof After => new AfterInterceptor($adviceCallback, $adviceOrder, $pointcutExpression), $interceptorAttribute instanceof Around => new AroundInterceptor($adviceCallback, $adviceOrder, $pointcutExpression), $interceptorAttribute instanceof AfterThrowing => new AfterThrowingInterceptor($adviceCallback, $adviceOrder, $pointcutExpression), - default => throw new UnexpectedValueException('Unsupported method meta class: ' . get_class($interceptorAttribute)), + default => throw new UnexpectedValueException('Unsupported method meta class: ' . $interceptorAttribute::class), }; } } diff --git a/src/Core/CachedAspectLoader.php b/src/Core/CachedAspectLoader.php index abd47c73..d9fdd975 100644 --- a/src/Core/CachedAspectLoader.php +++ b/src/Core/CachedAspectLoader.php @@ -50,7 +50,7 @@ class CachedAspectLoader extends AspectLoader public function __construct(AspectContainer $container, string $loaderId, array $options = []) { $this->cacheDir = $options['cacheDir'] ?? null; - $this->cacheFileMode = $options['cacheFileMode']; + $this->cacheFileMode = $options['cacheFileMode'] ?? 0770 & ~umask(); $this->loaderId = $loaderId; $this->container = $container; } @@ -74,21 +74,13 @@ public function load(Aspect $aspect): array return $loadedItems; } - /** - * {@inheritdoc} - */ - public function registerLoaderExtension(AspectLoaderExtension $loader): void - { - $this->loader->registerLoaderExtension($loader); - } - /** * {@inheritdoc} */ public function __get($name) { if ($name === 'loader') { - $this->loader = $this->container->get($this->loaderId); + $this->loader = $this->container->getService($this->loaderId); return $this->loader; } diff --git a/src/Core/Container.php b/src/Core/Container.php index e8a8d505..562783e0 100644 --- a/src/Core/Container.php +++ b/src/Core/Container.php @@ -13,91 +13,194 @@ namespace Go\Core; use Closure; +use Go\Aop\Aspect; +use Go\Aop\AspectException; +use Go\Aop\Pointcut\PointcutGrammar; +use Go\Aop\Pointcut\PointcutLexer; +use Go\Aop\Pointcut\PointcutParser; +use Go\Instrument\ClassLoading\CachePathManager; use OutOfBoundsException; +use ReflectionObject; /** * DI-container */ -abstract class Container implements AspectContainer +class Container implements AspectContainer { /** - * List of services in the container + * @var (array&array) Hashmap of items/services in the container */ - protected array $values = []; + private array $values = []; /** - * Store identifiers os services by tags + * @var (array&array>) Holds information about mapping of interface tags into identifiers */ - protected array $tags = []; + private array $tags = []; /** - * Set a service into the container + * Cached timestamp for resources, might be uninitialized if {@see self::hasAnyResourceChangedSince()} is not called yet + */ + private int $cachedMaxTimestamp; + + /** + * @var (array&array) Hashmap of resources for application + */ + private array $resources = []; + + /** + * Constructor for container * - * @param mixed $value Value to store + * @param array $resources [Optional] List of additional resources to track for container invalidation */ - public function set(string $id, $value, array $tags = []): void + public function __construct(array $resources = []) + { + $this->resources = array_combine($resources, $resources); + + $this->addLazy(PointcutLexer::class, fn() => new PointcutLexer()); + + $this->addLazy(PointcutParser::class, fn(AspectContainer $container) => new PointcutParser( + new PointcutGrammar($container) + )); + + $this->addLazy(AdviceMatcher::class, fn(AspectContainer $container) => new AdviceMatcher( + (bool) $container->getValue('kernel.interceptFunctions') + )); + + $this->addLazy(AspectLoader::class, function (AspectContainer $container) { + $lexer = $container->getService(PointcutLexer::class); + $parser = $container->getService(PointcutParser::class); + + return new AspectLoader( + $container, + new AttributeAspectLoaderExtension($lexer, $parser), + new IntroductionAspectExtension($lexer, $parser) + ); + }); + + $this->addLazy(CachedAspectLoader::class, function (AspectContainer $container) { + $options = $container->getValue('kernel.options'); + if (is_array($options) && !empty($options['cacheDir'])) { + $loader = new CachedAspectLoader($container, AspectLoader::class, $options); + } else { + $loader = $container->getService(AspectLoader::class); + } + + return $loader; + }); + + $this->addLazy(LazyAdvisorAccessor::class, fn(AspectContainer $container) => new LazyAdvisorAccessor( + $container, + $container->getService(CachedAspectLoader::class) + )); + + $this->addLazy(CachePathManager::class, fn(AspectContainer $container) => new CachePathManager( + $container->getService(AspectKernel::class) + )); + } + + final public function registerAspect(Aspect $aspect): void + { + $this->add($aspect::class, $aspect); + } + + final public function add(string $id, mixed $value): void { $this->values[$id] = $value; - foreach ($tags as $tag) { - $this->tags[$tag][] = $id; + + // For objects we would like to use interface names as tags, eg Pointcut, Advisor, Aspect, etc + if (is_object($value) && !$value instanceof Closure) { + $reflectionObject = new ReflectionObject($value); + foreach ($reflectionObject->getInterfaceNames() as $interfaceTagName) { + $this->tags[$interfaceTagName][] = $id; + } + // Also register corresponding file names to track freshness of container + $fileName = $reflectionObject->getFileName(); + if (is_string($fileName)) { + $this->addResource($fileName); + } } } - /** - * Set a shared value in the container - */ - public function share(string $id, Closure $value, array $tags = []): void + final public function getService(string $className): object { - $value = function ($container) use ($value) { - static $sharedValue; + if (!isset($this->values[$className])) { + throw new OutOfBoundsException("Value {$className} is not defined in the container"); + } + // Support for lazy-evaluation and initialization + if ($this->values[$className] instanceof Closure) { + return $this->values[$className]($this); + } + if (!$this->values[$className] instanceof $className) { + throw new AspectException("Service {$className} is not properly registered"); + } - if ($sharedValue === null) { - $sharedValue = $value($container); - } + return $this->values[$className]; + } + + final public function getValue(string $key): mixed + { + if (!isset($this->values[$key])) { + throw new OutOfBoundsException("Value {$key} is not defined in the container"); + } - return $sharedValue; - }; - $this->set($id, $value, $tags); + return $this->values[$key]; } - /** - * Return a service or value from the container - * - * @return mixed - * @throws OutOfBoundsException if service was not found - */ - public function get(string $id) + final public function has(string $id): bool + { + return isset($this->values[$id]); + } + + final public function getServicesByInterface(string $interfaceTagClassName): array { - if (!isset($this->values[$id])) { - throw new OutOfBoundsException("Value {$id} is not defined in the container"); + $values = []; + foreach (($this->tags[$interfaceTagClassName] ?? []) as $containerKey) { + $values[$containerKey] = $this->getValue($containerKey); } - if ($this->values[$id] instanceof Closure) { - return $this->values[$id]($this); + + return $values; + } + + final public function hasAnyResourceChangedSince(int $timestamp): bool + { + if (!isset($this->cachedMaxTimestamp)) { + $this->cachedMaxTimestamp = max(array_filter(array_map(filemtime(...), $this->resources)) + [0]); } - return $this->values[$id]; + return $this->cachedMaxTimestamp <= $timestamp; } /** - * Checks if item with specified id is present in the container + * Adds a link to the file resource into the container + * + * This set of resources is used later to check the freshness of cache + * + * @param string $resource Path to the resource */ - public function has(string $id): bool + final protected function addResource(string $resource): void { - return isset($this->values[$id]); + if (!isset($this->resources[$resource])) { + $this->resources[$resource] = $resource; + + // Invalidation of calculated timestamp + unset($this->cachedMaxTimestamp); + } } /** - * Return list of service tagged with marker + * Add value in the container, uses lazy-loading scheme to optimize init time + * + * @param Closure(AspectContainer $container): object $lazyDefinitionClosure */ - public function getByTag(string $tag): array + final protected function addLazy(string $id, Closure $lazyDefinitionClosure): void { - $result = []; - if (isset($this->tags[$tag])) { - foreach ($this->tags[$tag] as $id) { - $result[$id] = $this->get($id); - } - } + $this->add($id, function (self $container) use ($id, $lazyDefinitionClosure): object { + + $evaluatedLazyValue = $lazyDefinitionClosure($container); + // Here we just replace Closure with resolved value to optimize access + $container->values[$id] = $evaluatedLazyValue; - return $result; + return $evaluatedLazyValue; + }); } } diff --git a/src/Core/GoAspectContainer.php b/src/Core/GoAspectContainer.php deleted file mode 100644 index 4cbe9fc9..00000000 --- a/src/Core/GoAspectContainer.php +++ /dev/null @@ -1,175 +0,0 @@ - - * - * This source file is subject to the license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Go\Core; - -use Go\Aop\Advisor; -use Go\Aop\Aspect; -use Go\Aop\Pointcut; -use Go\Aop\Pointcut\PointcutGrammar; -use Go\Aop\Pointcut\PointcutLexer; -use Go\Aop\Pointcut\PointcutParser; -use Go\Instrument\ClassLoading\CachePathManager; -use ReflectionClass; - -/** - * Aspect container contains list of all pointcuts and advisors - */ -class GoAspectContainer extends Container -{ - /** - * List of resources for application - * - * @var string[] - */ - protected array $resources = []; - - /** - * Cached timestamp for resources - */ - protected int $maxTimestamp = 0; - - /** - * Constructor for container - */ - public function __construct() - { - // Register all services in the container - $this->share('aspect.loader', function (Container $container) { - $aspectLoader = new AspectLoader($container); - $lexer = $container->get('aspect.pointcut.lexer'); - $parser = $container->get('aspect.pointcut.parser'); - - // Register general aspect loader extension - $aspectLoader->registerLoaderExtension(new AttributeAspectLoaderExtension($lexer, $parser)); - $aspectLoader->registerLoaderExtension(new IntroductionAspectExtension($lexer, $parser)); - - return $aspectLoader; - }); - - $this->share('aspect.cached.loader', function (Container $container) { - $options = $container->get('kernel.options'); - if (!empty($options['cacheDir'])) { - $loader = new CachedAspectLoader( - $container, - 'aspect.loader', - $container->get('kernel.options') - ); - } else { - $loader = $container->get('aspect.loader'); - } - - return $loader; - }); - - $this->share('aspect.advisor.accessor', fn(Container $container) => new LazyAdvisorAccessor( - $container, - $container->get('aspect.cached.loader') - )); - - $this->share('aspect.advice_matcher', fn(Container $container) => new AdviceMatcher( - $container->get('kernel.interceptFunctions') - )); - - $this->share('aspect.cache.path.manager', fn(Container $container) => new CachePathManager($container->get('kernel'))); - - // Pointcut services - $this->share('aspect.pointcut.lexer', fn() => new PointcutLexer()); - $this->share('aspect.pointcut.parser', fn(Container $container) => new PointcutParser( - new PointcutGrammar($container) - )); - } - - /** - * Returns a pointcut by identifier - */ - public function getPointcut(string $id): Pointcut - { - return $this->get("pointcut.{$id}"); - } - - /** - * Store the pointcut in the container - */ - public function registerPointcut(Pointcut $pointcut, string $id): void - { - $this->set("pointcut.{$id}", $pointcut, ['pointcut']); - } - - /** - * Returns an advisor by identifier - */ - public function getAdvisor(string $id): Advisor - { - return $this->get("advisor.{$id}"); - } - - /** - * Store the advisor in the container - */ - public function registerAdvisor(Advisor $advisor, string $id): void - { - $this->set("advisor.{$id}", $advisor, ['advisor']); - } - - /** - * Returns an aspect by id or class name - */ - public function getAspect(string $aspectName): Aspect - { - return $this->get("aspect.{$aspectName}"); - } - - /** - * Register an aspect in the container - */ - public function registerAspect(Aspect $aspect): void - { - $refAspect = new ReflectionClass($aspect); - $this->set("aspect.{$refAspect->name}", $aspect, ['aspect']); - $this->addResource($refAspect->getFileName()); - } - - /** - * Add an AOP resource to the container - * Resources is used to check the freshness of AOP cache - * - * @param string $resource Path to the resource - */ - public function addResource(string $resource): void - { - $this->resources[] = $resource; - $this->maxTimestamp = 0; - } - - /** - * Returns list of AOP resources - */ - public function getResources(): array - { - return $this->resources; - } - - /** - * Checks the freshness of AOP cache - * - * @return bool Whether or not concrete file is fresh - */ - public function isFresh(int $timestamp): bool - { - if (!$this->maxTimestamp && !empty($this->resources)) { - $this->maxTimestamp = max(array_map('filemtime', $this->resources)); - } - - return $this->maxTimestamp <= $timestamp; - } -} diff --git a/src/Core/LazyAdvisorAccessor.php b/src/Core/LazyAdvisorAccessor.php index 42c39fbf..a521f1e8 100644 --- a/src/Core/LazyAdvisorAccessor.php +++ b/src/Core/LazyAdvisorAccessor.php @@ -15,6 +15,8 @@ use AllowDynamicProperties; use Go\Aop\Advice; use Go\Aop\Advisor; +use Go\Aop\Aspect; +use Go\Aop\AspectException; use InvalidArgumentException; /** @@ -50,14 +52,15 @@ public function __construct(AspectContainer $container, AspectLoader $loader) public function __get(string $name): Advice { if (!$this->container->has($name)) { - list(, $advisorName) = explode('.', $name); - list($aspect) = explode('->', $advisorName); - $aspectInstance = $this->container->getAspect($aspect); + [$aspectName] = explode('->', $name, 2); + if (!is_subclass_of($aspectName, Aspect::class)) { + throw new AspectException("{$aspectName} is not a valid aspect class"); + } + $aspectInstance = $this->container->getService($aspectName); $this->loader->loadAndRegister($aspectInstance); } - $advisor = $this->container->get($name); - + $advisor = $this->container->getValue($name); if (!$advisor instanceof Advisor) { throw new InvalidArgumentException("Reference {$name} is not an advisor"); } diff --git a/src/Instrument/ClassLoading/AopComposerLoader.php b/src/Instrument/ClassLoading/AopComposerLoader.php index 9550dc73..2d790aec 100644 --- a/src/Instrument/ClassLoading/AopComposerLoader.php +++ b/src/Instrument/ClassLoading/AopComposerLoader.php @@ -71,7 +71,7 @@ public function __construct(ClassLoader $original, AspectContainer $container, a $fileEnumerator = new Enumerator($options['appDir'], $options['includePaths'], $excludePaths); $this->fileEnumerator = $fileEnumerator; - $this->cacheState = $container->get('aspect.cache.path.manager')->queryCacheState(); + $this->cacheState = $container->getService(CachePathManager::class)->queryCacheState(); } /** diff --git a/src/Instrument/Transformer/CachingTransformer.php b/src/Instrument/Transformer/CachingTransformer.php index 8b225dd3..0f53bdbc 100644 --- a/src/Instrument/Transformer/CachingTransformer.php +++ b/src/Instrument/Transformer/CachingTransformer.php @@ -74,7 +74,7 @@ public function transform(StreamMetaData $metadata): string if ($cacheModified < $lastModified || (isset($cacheState['cacheUri']) && $cacheState['cacheUri'] !== $cacheUri) - || !$this->container->isFresh($cacheModified) + || !$this->container->hasAnyResourceChangedSince($cacheModified) ) { $processingResult = $this->processTransformers($metadata); if ($processingResult === self::RESULT_TRANSFORMED) { diff --git a/src/Instrument/Transformer/WeavingTransformer.php b/src/Instrument/Transformer/WeavingTransformer.php index 1788dfe9..317e3254 100644 --- a/src/Instrument/Transformer/WeavingTransformer.php +++ b/src/Instrument/Transformer/WeavingTransformer.php @@ -88,7 +88,7 @@ public function transform(StreamMetaData $metadata): string if (!empty($unloadedAspects)) { $this->loadAndRegisterAspects($unloadedAspects); } - $advisors = $this->container->getByTag('advisor'); + $advisors = $this->container->getServicesByInterface(Advisor::class); $namespaces = $parsedSource->getFileNamespaces(); @@ -254,7 +254,7 @@ private function processFunctions( $fileName = str_replace('\\', '/', $namespace->getName()) . '.php'; $functionFileName = $cacheDir . $fileName; - if (!file_exists($functionFileName) || !$this->container->isFresh(filemtime($functionFileName))) { + if (!file_exists($functionFileName) || !$this->container->hasAnyResourceChangedSince(filemtime($functionFileName))) { $functionAdvices = AbstractJoinpoint::flatAndSortAdvices($functionAdvices); $dirname = dirname($functionFileName); if (!file_exists($dirname)) { diff --git a/src/Proxy/ClassProxyGenerator.php b/src/Proxy/ClassProxyGenerator.php index 801548e0..ed904c82 100644 --- a/src/Proxy/ClassProxyGenerator.php +++ b/src/Proxy/ClassProxyGenerator.php @@ -177,12 +177,11 @@ public function generate(): string */ protected static function wrapWithJoinPoints(array $classAdvices, string $className): array { - /** @var ?LazyAdvisorAccessor $accessor */ static $accessor = null; if (!isset($accessor)) { $aspectKernel = AspectKernel::getInstance(); - $accessor = $aspectKernel->getContainer()->get('aspect.advisor.accessor'); + $accessor = $aspectKernel->getContainer()->getService(LazyAdvisorAccessor::class); } $joinPoints = []; diff --git a/src/Proxy/FunctionProxyGenerator.php b/src/Proxy/FunctionProxyGenerator.php index 07aa4944..f32be740 100644 --- a/src/Proxy/FunctionProxyGenerator.php +++ b/src/Proxy/FunctionProxyGenerator.php @@ -15,6 +15,7 @@ use Go\Aop\Framework\ReflectionFunctionInvocation; use Go\Core\AspectContainer; use Go\Core\AspectKernel; +use Go\Core\LazyAdvisorAccessor; use Go\ParserReflection\ReflectionFileNamespace; use Go\Proxy\Part\FunctionCallArgumentListGenerator; use Go\Proxy\Part\InterceptedFunctionGenerator; @@ -79,7 +80,7 @@ public static function getJoinPoint(string $functionName, array $adviceNames): R static $accessor; if ($accessor === null) { - $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor'); + $accessor = AspectKernel::getInstance()->getContainer()->getService(LazyAdvisorAccessor::class); } $filledAdvices = []; diff --git a/src/Proxy/TraitProxyGenerator.php b/src/Proxy/TraitProxyGenerator.php index 9587e013..c301f890 100644 --- a/src/Proxy/TraitProxyGenerator.php +++ b/src/Proxy/TraitProxyGenerator.php @@ -15,6 +15,7 @@ use Go\Aop\Intercept\MethodInvocation; use Go\Core\AspectContainer; use Go\Core\AspectKernel; +use Go\Core\LazyAdvisorAccessor; use Go\Proxy\Part\FunctionCallArgumentListGenerator; use ReflectionClass; use ReflectionMethod; @@ -87,7 +88,7 @@ public static function getJoinPoint( if ($accessor === null) { $aspectKernel = AspectKernel::getInstance(); - $accessor = $aspectKernel->getContainer()->get('aspect.advisor.accessor'); + $accessor = $aspectKernel->getContainer()->getService(LazyAdvisorAccessor::class); } $filledAdvices = []; diff --git a/tests/Go/Core/ContainerTest.php b/tests/Go/Core/ContainerTest.php new file mode 100644 index 00000000..965e2054 --- /dev/null +++ b/tests/Go/Core/ContainerTest.php @@ -0,0 +1,164 @@ + + * + * This source file is subject to the license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Go\Core; + +use Go\Aop\Advisor; +use Go\Aop\Aspect; +use Go\Aop\AspectException; +use Go\Aop\Pointcut; +use Go\Aop\Pointcut\PointcutLexer; +use Go\Aop\Pointcut\PointcutParser; +use Go\Stubs\First; +use PHPUnit\Framework\TestCase; +use stdClass; + +class ContainerTest extends TestCase +{ + protected AspectContainer $container; + + protected function setUp(): void + { + $this->container = new Container(); + $this->container->add(AspectKernel::class, $this->createMock(AspectKernel::class)); + $this->container->add('kernel.options', ['cacheDir' => '/tmp']); + $this->container->add('kernel.interceptFunctions', false); + } + + /** + * Tests that all internal services are registered and loadable + * @param class-string $serviceId + */ + #[\PHPUnit\Framework\Attributes\DataProvider('lazyInternalServices')] + public function testAllServicesAreConfigured(string $serviceId): void + { + $service = $this->container->getService($serviceId); + $this->assertNotNull($service); + } + + /** + * @return class-string[][] + */ + public static function lazyInternalServices(): array + { + return [ + PointcutLexer::class => [PointcutLexer::class], + PointcutParser::class => [PointcutParser::class], + AdviceMatcher::class => [AdviceMatcher::class], + AspectLoader::class => [AspectLoader::class], + CachedAspectLoader::class => [CachedAspectLoader::class], + LazyAdvisorAccessor::class => [LazyAdvisorAccessor::class], + // [CachePathManager::class], // Need to politely switch to options instead of whole kernel + ]; + } + + /** + * Tests that pointcut can be registered and accessed + */ + public function testPointcutCanBeRegisteredAndReceived(): void + { + $pointcut = $this->createMock(Pointcut::class); + $this->container->add('test', $pointcut); + + $this->assertSame($pointcut, $this->container->getValue('test')); + // Verify that tag is working + $pointcuts = $this->container->getServicesByInterface(Pointcut::class); + $this->assertSame(['test' => $pointcut], $pointcuts); + } + + /** + * Tests that pointcut can be registered and accessed + */ + public function testAdvisorCanBeRegistered(): void + { + $advisor = $this->createMock(Advisor::class); + $this->container->add('test', $advisor); + + $this->assertSame($advisor, $this->container->getValue('test')); + + // Verify that tag is working + $advisors = $this->container->getServicesByInterface(Advisor::class); + $this->assertSame(['test' => $advisor], $advisors); + } + + /** + * Tests that aspect can be registered and accessed + */ + public function testAspectCanBeRegisteredAndReceived(): void + { + $aspect = $this->createMock(Aspect::class); + $aspectClass = $aspect::class; + + $this->container->registerAspect($aspect); + + $this->assertSame($aspect, $this->container->getService($aspectClass)); + // Verify that tag is working + $aspects = $this->container->getServicesByInterface(Aspect::class); + $this->assertSame([$aspectClass => $aspect], $aspects); + } + + /** + * Tests that container resources can be added and isFresh works correctly + */ + public function testResourceManagement(): void + { + // Without resources this should be always true + $isFresh = $this->container->hasAnyResourceChangedSince(time()); + $this->assertTrue($isFresh); + + $this->container->add(First::class, new First()); + $filename = (new \ReflectionClass(First::class))->getFileName(); + $this->assertNotFalse($filename); + $this->assertFileExists($filename); + + $realMtime = filemtime($filename); + $isFresh = $this->container->hasAnyResourceChangedSince($realMtime - 3600); + $this->assertFalse($isFresh); + + $isFresh = $this->container->hasAnyResourceChangedSince($realMtime + 3600); + $this->assertTrue($isFresh); + } + + public function testHasMethod(): void + { + $this->assertFalse($this->container->has('test')); + + $advisor = $this->createMock(Advisor::class); + $this->container->add('test', $advisor); + + $this->assertTrue($this->container->has('test')); + } + + public function testGetServiceThrowsOutOfBoundsExceptionOnUnknown(): void + { + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessageMatches('/Value stdClass is not defined/'); + $this->container->getService(stdClass::class); + } + + public function testGetValueThrowsOutOfBoundsExceptionOnUnknown(): void + { + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessageMatches('/Value some.key is not defined/'); + $this->container->getValue('some.key'); + } + + public function testGetServiceEnsuresThatKeyAndReturnedTypeMatches(): void + { + $this->expectException(AspectException::class); + $this->expectExceptionMessage('Service ' . First::class . ' is not properly registered'); + + // Emulation of incorrect types + $this->container->add(First::class, new stdClass()); + $this->container->getService(First::class); + } +} diff --git a/tests/Go/Core/GoAspectContainerTest.php b/tests/Go/Core/GoAspectContainerTest.php deleted file mode 100644 index ee3af993..00000000 --- a/tests/Go/Core/GoAspectContainerTest.php +++ /dev/null @@ -1,111 +0,0 @@ - - * - * This source file is subject to the license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Go\Core; - -use Go\Aop\Advisor; -use Go\Aop\Aspect; -use Go\Aop\Pointcut; -use PHPUnit\Framework\TestCase; - -class GoAspectContainerTest extends TestCase -{ - protected GoAspectContainer $container; - - protected function setUp(): void - { - $this->container = new GoAspectContainer(); - $this->container->set('kernel.options', []); - $this->container->set('kernel.interceptFunctions', false); - } - - /** - * Tests that all services are registered - */ - #[\PHPUnit\Framework\Attributes\DataProvider('internalServicesList')] - public function testAllServicesAreConfigured(string $serviceId): void - { - $service = $this->container->get($serviceId); - $this->assertNotNull($service); - } - - public static function internalServicesList(): array - { - return [ - ['aspect.loader'], - ['aspect.advice_matcher'], - ['aspect.pointcut.lexer'], - ['aspect.pointcut.parser'], - ]; - } - - /** - * Tests that pointcut can be registered and accessed - */ - public function testPointcutCanBeRegisteredAndReceived(): void - { - $pointcut = $this->createMock(Pointcut::class); - $this->container->registerPointcut($pointcut, 'test'); - - $this->assertSame($pointcut, $this->container->getPointcut('test')); - // Verify that tag is working - $pointcuts = $this->container->getByTag('pointcut'); - $this->assertSame(['pointcut.test' => $pointcut], $pointcuts); - } - - /** - * Tests that pointcut can be registered and accessed - */ - public function testAdvisorCanBeRegistered(): void - { - $advisor = $this->createMock(Advisor::class); - $this->container->registerAdvisor($advisor, 'test'); - - // Verify that tag is working - $advisors = $this->container->getByTag('advisor'); - $this->assertSame(['advisor.test' => $advisor], $advisors); - } - - /** - * Tests that aspect can be registered and accessed - */ - public function testAspectCanBeRegisteredAndReceived(): void - { - $aspect = $this->createMock(Aspect::class); - $aspectClass = get_class($aspect); - - $this->container->registerAspect($aspect); - - $this->assertSame($aspect, $this->container->getAspect($aspectClass)); - // Verify that tag is working - $aspects = $this->container->getByTag('aspect'); - $this->assertSame(["aspect.{$aspectClass}" => $aspect], $aspects); - } - - /** - * Tests that container resources can be added and isFresh works correctly - */ - public function testResourceManagement(): void - { - // Without resources this should be always true - $isFresh = $this->container->isFresh(time()); - $this->assertTrue($isFresh); - - $this->container->addResource(__FILE__); - $realMtime = filemtime(__FILE__); - $isFresh = $this->container->isFresh($realMtime - 3600); - $this->assertFalse($isFresh); - - $isFresh = $this->container->isFresh($realMtime + 3600); - $this->assertTrue($isFresh); - } -} diff --git a/tests/Go/Functional/ClassWeavingTest.php b/tests/Go/Functional/ClassWeavingTest.php index 7631bed8..d9c0477d 100644 --- a/tests/Go/Functional/ClassWeavingTest.php +++ b/tests/Go/Functional/ClassWeavingTest.php @@ -22,11 +22,11 @@ class ClassWeavingTest extends BaseFunctionalTestCase public function testPropertyWeaving() { // it weaves Main class public and protected properties - $this->assertPropertyWoven(Main::class, 'publicClassProperty', 'advisor.Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); - $this->assertPropertyWoven(Main::class, 'protectedClassProperty', 'advisor.Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); + $this->assertPropertyWoven(Main::class, 'publicClassProperty', 'Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); + $this->assertPropertyWoven(Main::class, 'protectedClassProperty', 'Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); // it does not weaves Main class private property - $this->assertPropertyNotWoven(Main::class, 'privateClassProperty', 'advisor.Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); + $this->assertPropertyNotWoven(Main::class, 'privateClassProperty', 'Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty'); } /** @@ -38,9 +38,9 @@ public function testItDoesNotWeaveAbstractMethods() $this->assertClassIsWoven(Main::class); // it weaves Main class methods - $this->assertMethodWoven(Main::class, 'doSomething', 'advisor.Go\\Tests\\TestProject\\Aspect\\LoggingAspect->beforeMethod', 0); - $this->assertMethodWoven(Main::class, 'doSomething', 'advisor.Go\\Tests\\TestProject\\Aspect\\DoSomethingAspect->afterDoSomething', 1); - $this->assertMethodWoven(Main::class, 'doSomethingElse', 'advisor.Go\\Tests\\TestProject\\Aspect\\DoSomethingAspect->afterDoSomething'); + $this->assertMethodWoven(Main::class, 'doSomething', 'Go\\Tests\\TestProject\\Aspect\\LoggingAspect->beforeMethod', 0); + $this->assertMethodWoven(Main::class, 'doSomething', 'Go\\Tests\\TestProject\\Aspect\\DoSomethingAspect->afterDoSomething', 1); + $this->assertMethodWoven(Main::class, 'doSomethingElse', 'Go\\Tests\\TestProject\\Aspect\\DoSomethingAspect->afterDoSomething'); // it does not weaves AbstractBar class $this->assertClassIsNotWoven(AbstractBar::class); @@ -48,8 +48,8 @@ public function testItDoesNotWeaveAbstractMethods() public function testClassInitializationWeaving() { - $this->assertClassInitializationWoven(Main::class, 'advisor.Go\\Tests\\TestProject\\Aspect\\InitializationAspect->beforeInstanceInitialization'); - $this->assertClassStaticInitializationWoven(Main::class, 'advisor.Go\\Tests\\TestProject\\Aspect\\InitializationAspect->afterClassStaticInitialization'); + $this->assertClassInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->beforeInstanceInitialization'); + $this->assertClassStaticInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->afterClassStaticInitialization'); } public function testItWeavesFinalClasses() diff --git a/tests/Go/Functional/Issue293Test.php b/tests/Go/Functional/Issue293Test.php index bdf5cdfb..f9e95c0f 100644 --- a/tests/Go/Functional/Issue293Test.php +++ b/tests/Go/Functional/Issue293Test.php @@ -23,8 +23,8 @@ class Issue293Test extends BaseFunctionalTestCase public function testItDoesNotWeaveDynamicMethodsForComplexStaticPointcut() { $this->assertClassIsWoven(Issue293StaticMembers::class); - $this->assertStaticMethodWoven(Issue293StaticMembers::class, 'doSomething', 'advisor.Go\\Tests\\TestProject\\Aspect\\Issue293Aspect->afterPublicOrProtectedStaticMethods'); - $this->assertStaticMethodWoven(Issue293StaticMembers::class, 'doSomethingElse', 'advisor.Go\\Tests\\TestProject\\Aspect\\Issue293Aspect->afterPublicOrProtectedStaticMethods'); + $this->assertStaticMethodWoven(Issue293StaticMembers::class, 'doSomething', 'Go\\Tests\\TestProject\\Aspect\\Issue293Aspect->afterPublicOrProtectedStaticMethods'); + $this->assertStaticMethodWoven(Issue293StaticMembers::class, 'doSomethingElse', 'Go\\Tests\\TestProject\\Aspect\\Issue293Aspect->afterPublicOrProtectedStaticMethods'); // it does not weaves Issue293DynamicMembers class $this->assertClassIsNotWoven(Issue293DynamicMembers::class); diff --git a/tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php b/tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php index 0ae79a71..a7c030fa 100644 --- a/tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php +++ b/tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php @@ -12,11 +12,10 @@ namespace Go\Instrument\Transformer; +use Go\Core\AspectContainer; use Go\Core\AspectKernel; -use Go\Core\GoAspectContainer; use Go\Instrument\ClassLoading\CachePathManager; use Go\Instrument\PathResolver; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use TypeError; @@ -38,7 +37,7 @@ public function setUp(): void 'debug' => false, 'features' => 0 ], - $this->createMock(GoAspectContainer::class) + $this->createMock(AspectContainer::class) ); $cachePathManager = $this ->getMockBuilder(CachePathManager::class) @@ -51,7 +50,7 @@ public function setUp(): void /** * Returns a mock for kernel */ - protected function getKernelMock(array $options, GoAspectContainer $container): AspectKernel + protected function getKernelMock(array $options, AspectContainer $container): AspectKernel { $mock = $this->getMockForAbstractClass( AspectKernel::class, diff --git a/tests/Go/Instrument/Transformer/WeavingTransformerTest.php b/tests/Go/Instrument/Transformer/WeavingTransformerTest.php index 31492699..c0e1ea4f 100644 --- a/tests/Go/Instrument/Transformer/WeavingTransformerTest.php +++ b/tests/Go/Instrument/Transformer/WeavingTransformerTest.php @@ -12,6 +12,7 @@ namespace Go\Instrument\Transformer; +use Go\Aop\Advisor; use Go\Core\AdviceMatcherInterface; use Go\Core\AspectContainer; use Go\Core\AspectKernel; @@ -302,10 +303,10 @@ private function getContainerMock(): AspectContainer $container = $this->createMock(AspectContainer::class); $container - ->method('getByTag') - ->will($this->returnValueMap([ - ['advisor', []] - ])); + ->method('getServicesByInterface') + ->willReturnMap([ + [Advisor::class, []] + ]); return $container; }