Skip to content

[Feature] Pointcut namespace refactoring #500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions phpstan-baseline.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<?php declare(strict_types = 1);

$ignoreErrors = [];
$ignoreErrors[] = [
'message' => '#^Instanceof between stdClass and Go\\\\ParserReflection\\\\ReflectionFileNamespace will always evaluate to false\\.$#',
'count' => 1,
'path' => __DIR__ . '/src/Aop/Support/SimpleNamespaceFilter.php',
];
$ignoreErrors[] = [
'message' => '#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\<object\\>\\:\\:\\$table \\(array\\{name\\: string, schema\\?\\: string, indexes\\?\\: array, uniqueConstraints\\?\\: array, options\\?\\: array\\<string, mixed\\>, quoted\\?\\: bool\\}\\) does not accept array\\{\\}\\.$#',
'count' => 1,
Expand Down
8 changes: 4 additions & 4 deletions src/Aop/Framework/DynamicInvocationMatcherInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use Go\Aop\Intercept\Interceptor;
use Go\Aop\Intercept\Joinpoint;
use Go\Aop\Intercept\MethodInvocation;
use Go\Aop\PointFilter;
use Go\Aop\Pointcut;
use ReflectionClass;

/**
Expand All @@ -27,10 +27,10 @@
readonly class DynamicInvocationMatcherInterceptor implements Interceptor
{
/**
* Dynamic matcher constructor
* Dynamic invocation matcher constructor
*/
public function __construct(
private PointFilter $pointFilter,
private Pointcut $pointcut,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line indented incorrectly; expected 4 spaces, found 8

private Interceptor $interceptor
){}

Expand All @@ -40,7 +40,7 @@ final public function invoke(Joinpoint $joinpoint): mixed
$method = $joinpoint->getMethod();
$context = $joinpoint->getThis() ?? $joinpoint->getScope();
$contextClass = new ReflectionClass($context);
if ($this->pointFilter->matches($method, $contextClass, $context, $joinpoint->getArguments())) {
if ($this->pointcut->matches($contextClass, $method, $context, $joinpoint->getArguments())) {
return $this->interceptor->invoke($joinpoint);
}
}
Expand Down
31 changes: 0 additions & 31 deletions src/Aop/IntroductionAdvisor.php

This file was deleted.

57 changes: 0 additions & 57 deletions src/Aop/PointFilter.php

This file was deleted.

76 changes: 68 additions & 8 deletions src/Aop/Pointcut.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,77 @@

namespace Go\Aop;

use Go\ParserReflection\ReflectionFileNamespace;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;

/**
* Pointcut realization for PHP
* Pointcut is responsible for matching any reflection items both statically and dynamically.
*
* Pointcut may be evaluated statically or at runtime (dynamically).
* Matcher uses smart technique of matching elements, consisting of several stages described below.
*
* <b>Static matching</b>
*
* First stage of static matching involves context only (just one argument). This pre-stage is used to optimize
* filtering on matcher side to avoid nested loops of checks. For example, if we have a method pointcut, but
* it doesn't match first with class, then we don't need to scan all methods at all and can exit earlier.
*
* Here is a mapping of context for different static joinpoints:
* - For any traits or classes, context will be `ReflectionClass` corresponding to the given class or trait.
* - For any functions, context will be `ReflectionFileNamespace` where internal function is analyzed.
* - For any methods or properties, context will be `ReflectionClass` which is currently analysed (even for inherited items)
*
* Second stage of static matching uses exactly two arguments (context and reflector). Filter then fully checks
* static information from reflection to make a decision about matching of given point.
*
* At this stage we can verify names, attributes, signature, parameters, types, etc.
*
* Pointcuts are defined as a predicate over the syntax-tree of the program, and define an interface that constrains
* which elements of the base program are exposed by the pointcut. A pointcut picks out certain join points and values
* at those points
* If point filter is not dynamic {@see self::KIND_DYNAMIC}, then evaluation ends here statically,
* and generated code will not contain any runtime checks for given point filter, allowing for better performance.
*
* <b>Dynamic matching</b>
*
* If instance of filter is dynamic and uses {@see self::KIND_DYNAMIC} flag, then after static matching which has been
* used to prepare a dynamic hook, framework will call our pointcut again in runtime for dynamic matching.
*
* This dynamic matching stage uses full information about given join point, including possible instance/scope and
* arguments for a particular point.
*/
interface Pointcut extends PointFilter
interface Pointcut
{
public const KIND_METHOD = 1;
public const KIND_PROPERTY = 2;
public const KIND_CLASS = 4;
public const KIND_TRAIT = 8;
public const KIND_FUNCTION = 16;
public const KIND_INIT = 32;
public const KIND_STATIC_INIT = 64;
public const KIND_ALL = 127;
public const KIND_DYNAMIC = 256;
public const KIND_INTRODUCTION = 512;

/**
* Returns the kind of point filter
*/
public function getKind(): int;

/**
* Return the class filter for this pointcut.
* Performs matching of point of code, returns true if point matches
*
* @param ReflectionClass<T>|ReflectionFileNamespace $context Related context, can be class or file namespace
* @param ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector Specific part of code, can be any Reflection class
* @param null|(string&class-string<T>)|(object&T) $instanceOrScope Invocation instance or string for static calls
* @param null|array<mixed> $arguments Dynamic arguments for method
*
* @template T of object
*/
public function getClassFilter(): PointFilter;
}
public function matches(
ReflectionClass|ReflectionFileNamespace $context,
ReflectionMethod|ReflectionProperty|ReflectionFunction $reflector = null,
object|string $instanceOrScope = null,
array $arguments = null
): bool;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 1 newline at end of file; 0 found

97 changes: 36 additions & 61 deletions src/Aop/Pointcut/AndPointcut.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

declare(strict_types=1);
declare(strict_types = 1);
/*
* Go! AOP framework
*
Expand All @@ -13,87 +13,62 @@
namespace Go\Aop\Pointcut;

use Go\Aop\Pointcut;
use Go\Aop\Support\AndPointFilter;
use Go\ParserReflection\ReflectionFileNamespace;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;

/**
* Logical "AND" pointcut that combines two simple pointcuts
* Logical "and" pointcut filter.
*/
class AndPointcut implements Pointcut
final readonly class AndPointcut implements Pointcut
{
use PointcutClassFilterTrait;

/**
* First pointcut
*/
protected Pointcut $first;

/**
* Second pointcut
* Kind of pointcut
*/
protected Pointcut $second;
private int $pointcutKind;

/**
* Returns pointcut kind
* List of Pointcut to combine with "AND"
*
* @var array<Pointcut>
*/
protected int $kind;
private array $pointcuts;

/**
* "And" pointcut constructor
* And constructor
*/
public function __construct(Pointcut $first, Pointcut $second)
public function __construct(int $pointcutKind = null, Pointcut ...$pointcuts)
{
$this->first = $first;
$this->second = $second;
$this->kind = $first->getKind() & $second->getKind();

$this->classFilter = new AndPointFilter($first->getClassFilter(), $second->getClassFilter());
// If we don't have specified kind, it will be calculated as intersection then
if (!isset($pointcutKind)) {
$pointcutKind = -1;
foreach ($pointcuts as $singlePointcut) {
$pointcutKind &= $singlePointcut->getKind();
}
}
$this->pointcutKind = $pointcutKind;
$this->pointcuts = $pointcuts;
}

/**
* Performs matching of point of code
*
* @param mixed $point Specific part of code, can be any Reflection class
* @param null|mixed $context Related context, can be class or namespace
* @param null|string|object $instance Invocation instance or string for static calls
* @param null|array $arguments Dynamic arguments for method
*/
public function matches($point, $context = null, $instance = null, array $arguments = null): bool
{
return $this->matchPart($this->first, $point, $context, $instance, $arguments)
&& $this->matchPart($this->second, $point, $context, $instance, $arguments);
public function matches(
ReflectionClass|ReflectionFileNamespace $context,
ReflectionMethod|ReflectionProperty|ReflectionFunction $reflector = null,
object|string $instanceOrScope = null,
array $arguments = null
): bool {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found 0 spaces

foreach ($this->pointcuts as $singlePointcut) {
if (!$singlePointcut->matches($context, $reflector, $instanceOrScope, $arguments)) {
return false;
}
}

return true;
}

/**
* Returns the kind of point filter
*/
public function getKind(): int
{
return $this->kind;
}

/**
* Checks if point filter matches the point
*
* @param Pointcut $pointcut
* @param ReflectionMethod|ReflectionProperty|ReflectionClass $point
* @param mixed $context Related context, can be class or namespace
* @param object|string|null $instance [Optional] Instance for dynamic matching
* @param array|null $arguments [Optional] Extra arguments for dynamic
* matching
*
* @return bool
*/
protected function matchPart(
Pointcut $pointcut,
$point,
$context = null,
$instance = null,
array $arguments = null
): bool {
return $pointcut->matches($point, $context, $instance, $arguments)
&& $pointcut->getClassFilter()->matches($context);
return $this->pointcutKind;
}
}
Loading
Loading