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; }