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