Skip to content

Commit e2d30bf

Browse files
authored
Merge pull request #500 from goaop/feature/pointcut-namespace-refactoring
[Feature] Pointcut namespace refactoring
2 parents 67f6be5 + ca01ea8 commit e2d30bf

File tree

67 files changed

+1886
-2117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1886
-2117
lines changed

Diff for: phpstan-baseline.php

-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
<?php declare(strict_types = 1);
22

33
$ignoreErrors = [];
4-
$ignoreErrors[] = [
5-
'message' => '#^Instanceof between stdClass and Go\\\\ParserReflection\\\\ReflectionFileNamespace will always evaluate to false\\.$#',
6-
'count' => 1,
7-
'path' => __DIR__ . '/src/Aop/Support/SimpleNamespaceFilter.php',
8-
];
94
$ignoreErrors[] = [
105
'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\\{\\}\\.$#',
116
'count' => 1,

Diff for: src/Aop/Framework/DynamicInvocationMatcherInterceptor.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Go\Aop\Intercept\Interceptor;
1616
use Go\Aop\Intercept\Joinpoint;
1717
use Go\Aop\Intercept\MethodInvocation;
18-
use Go\Aop\PointFilter;
18+
use Go\Aop\Pointcut;
1919
use ReflectionClass;
2020

2121
/**
@@ -27,10 +27,10 @@
2727
readonly class DynamicInvocationMatcherInterceptor implements Interceptor
2828
{
2929
/**
30-
* Dynamic matcher constructor
30+
* Dynamic invocation matcher constructor
3131
*/
3232
public function __construct(
33-
private PointFilter $pointFilter,
33+
private Pointcut $pointcut,
3434
private Interceptor $interceptor
3535
){}
3636

@@ -40,7 +40,7 @@ final public function invoke(Joinpoint $joinpoint): mixed
4040
$method = $joinpoint->getMethod();
4141
$context = $joinpoint->getThis() ?? $joinpoint->getScope();
4242
$contextClass = new ReflectionClass($context);
43-
if ($this->pointFilter->matches($method, $contextClass, $context, $joinpoint->getArguments())) {
43+
if ($this->pointcut->matches($contextClass, $method, $context, $joinpoint->getArguments())) {
4444
return $this->interceptor->invoke($joinpoint);
4545
}
4646
}

Diff for: src/Aop/IntroductionAdvisor.php

-31
This file was deleted.

Diff for: src/Aop/PointFilter.php

-57
This file was deleted.

Diff for: src/Aop/Pointcut.php

+68-8
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,77 @@
1212

1313
namespace Go\Aop;
1414

15+
use Go\ParserReflection\ReflectionFileNamespace;
16+
use ReflectionClass;
17+
use ReflectionFunction;
18+
use ReflectionMethod;
19+
use ReflectionProperty;
20+
1521
/**
16-
* Pointcut realization for PHP
22+
* Pointcut is responsible for matching any reflection items both statically and dynamically.
23+
*
24+
* Pointcut may be evaluated statically or at runtime (dynamically).
25+
* Matcher uses smart technique of matching elements, consisting of several stages described below.
26+
*
27+
* <b>Static matching</b>
28+
*
29+
* First stage of static matching involves context only (just one argument). This pre-stage is used to optimize
30+
* filtering on matcher side to avoid nested loops of checks. For example, if we have a method pointcut, but
31+
* it doesn't match first with class, then we don't need to scan all methods at all and can exit earlier.
32+
*
33+
* Here is a mapping of context for different static joinpoints:
34+
* - For any traits or classes, context will be `ReflectionClass` corresponding to the given class or trait.
35+
* - For any functions, context will be `ReflectionFileNamespace` where internal function is analyzed.
36+
* - For any methods or properties, context will be `ReflectionClass` which is currently analysed (even for inherited items)
37+
*
38+
* Second stage of static matching uses exactly two arguments (context and reflector). Filter then fully checks
39+
* static information from reflection to make a decision about matching of given point.
40+
*
41+
* At this stage we can verify names, attributes, signature, parameters, types, etc.
1742
*
18-
* Pointcuts are defined as a predicate over the syntax-tree of the program, and define an interface that constrains
19-
* which elements of the base program are exposed by the pointcut. A pointcut picks out certain join points and values
20-
* at those points
43+
* If point filter is not dynamic {@see self::KIND_DYNAMIC}, then evaluation ends here statically,
44+
* and generated code will not contain any runtime checks for given point filter, allowing for better performance.
45+
*
46+
* <b>Dynamic matching</b>
47+
*
48+
* If instance of filter is dynamic and uses {@see self::KIND_DYNAMIC} flag, then after static matching which has been
49+
* used to prepare a dynamic hook, framework will call our pointcut again in runtime for dynamic matching.
50+
*
51+
* This dynamic matching stage uses full information about given join point, including possible instance/scope and
52+
* arguments for a particular point.
2153
*/
22-
interface Pointcut extends PointFilter
54+
interface Pointcut
2355
{
56+
public const KIND_METHOD = 1;
57+
public const KIND_PROPERTY = 2;
58+
public const KIND_CLASS = 4;
59+
public const KIND_TRAIT = 8;
60+
public const KIND_FUNCTION = 16;
61+
public const KIND_INIT = 32;
62+
public const KIND_STATIC_INIT = 64;
63+
public const KIND_ALL = 127;
64+
public const KIND_DYNAMIC = 256;
65+
public const KIND_INTRODUCTION = 512;
66+
67+
/**
68+
* Returns the kind of point filter
69+
*/
70+
public function getKind(): int;
71+
2472
/**
25-
* Return the class filter for this pointcut.
73+
* Performs matching of point of code, returns true if point matches
74+
*
75+
* @param ReflectionClass<T>|ReflectionFileNamespace $context Related context, can be class or file namespace
76+
* @param ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector Specific part of code, can be any Reflection class
77+
* @param null|(string&class-string<T>)|(object&T) $instanceOrScope Invocation instance or string for static calls
78+
* @param null|array<mixed> $arguments Dynamic arguments for method
79+
*
80+
* @template T of object
2681
*/
27-
public function getClassFilter(): PointFilter;
28-
}
82+
public function matches(
83+
ReflectionClass|ReflectionFileNamespace $context,
84+
ReflectionMethod|ReflectionProperty|ReflectionFunction $reflector = null,
85+
object|string $instanceOrScope = null,
86+
array $arguments = null
87+
): bool;
88+
}

Diff for: src/Aop/Pointcut/AndPointcut.php

+36-61
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
declare(strict_types=1);
3+
declare(strict_types = 1);
44
/*
55
* Go! AOP framework
66
*
@@ -13,87 +13,62 @@
1313
namespace Go\Aop\Pointcut;
1414

1515
use Go\Aop\Pointcut;
16-
use Go\Aop\Support\AndPointFilter;
16+
use Go\ParserReflection\ReflectionFileNamespace;
1717
use ReflectionClass;
18+
use ReflectionFunction;
1819
use ReflectionMethod;
1920
use ReflectionProperty;
2021

2122
/**
22-
* Logical "AND" pointcut that combines two simple pointcuts
23+
* Logical "and" pointcut filter.
2324
*/
24-
class AndPointcut implements Pointcut
25+
final readonly class AndPointcut implements Pointcut
2526
{
26-
use PointcutClassFilterTrait;
27-
28-
/**
29-
* First pointcut
30-
*/
31-
protected Pointcut $first;
32-
3327
/**
34-
* Second pointcut
28+
* Kind of pointcut
3529
*/
36-
protected Pointcut $second;
30+
private int $pointcutKind;
3731

3832
/**
39-
* Returns pointcut kind
33+
* List of Pointcut to combine with "AND"
34+
*
35+
* @var array<Pointcut>
4036
*/
41-
protected int $kind;
37+
private array $pointcuts;
4238

4339
/**
44-
* "And" pointcut constructor
40+
* And constructor
4541
*/
46-
public function __construct(Pointcut $first, Pointcut $second)
42+
public function __construct(int $pointcutKind = null, Pointcut ...$pointcuts)
4743
{
48-
$this->first = $first;
49-
$this->second = $second;
50-
$this->kind = $first->getKind() & $second->getKind();
51-
52-
$this->classFilter = new AndPointFilter($first->getClassFilter(), $second->getClassFilter());
44+
// If we don't have specified kind, it will be calculated as intersection then
45+
if (!isset($pointcutKind)) {
46+
$pointcutKind = -1;
47+
foreach ($pointcuts as $singlePointcut) {
48+
$pointcutKind &= $singlePointcut->getKind();
49+
}
50+
}
51+
$this->pointcutKind = $pointcutKind;
52+
$this->pointcuts = $pointcuts;
5353
}
5454

55-
/**
56-
* Performs matching of point of code
57-
*
58-
* @param mixed $point Specific part of code, can be any Reflection class
59-
* @param null|mixed $context Related context, can be class or namespace
60-
* @param null|string|object $instance Invocation instance or string for static calls
61-
* @param null|array $arguments Dynamic arguments for method
62-
*/
63-
public function matches($point, $context = null, $instance = null, array $arguments = null): bool
64-
{
65-
return $this->matchPart($this->first, $point, $context, $instance, $arguments)
66-
&& $this->matchPart($this->second, $point, $context, $instance, $arguments);
55+
public function matches(
56+
ReflectionClass|ReflectionFileNamespace $context,
57+
ReflectionMethod|ReflectionProperty|ReflectionFunction $reflector = null,
58+
object|string $instanceOrScope = null,
59+
array $arguments = null
60+
): bool {
61+
foreach ($this->pointcuts as $singlePointcut) {
62+
if (!$singlePointcut->matches($context, $reflector, $instanceOrScope, $arguments)) {
63+
return false;
64+
}
65+
}
66+
67+
return true;
6768
}
6869

69-
/**
70-
* Returns the kind of point filter
71-
*/
7270
public function getKind(): int
7371
{
74-
return $this->kind;
75-
}
76-
77-
/**
78-
* Checks if point filter matches the point
79-
*
80-
* @param Pointcut $pointcut
81-
* @param ReflectionMethod|ReflectionProperty|ReflectionClass $point
82-
* @param mixed $context Related context, can be class or namespace
83-
* @param object|string|null $instance [Optional] Instance for dynamic matching
84-
* @param array|null $arguments [Optional] Extra arguments for dynamic
85-
* matching
86-
*
87-
* @return bool
88-
*/
89-
protected function matchPart(
90-
Pointcut $pointcut,
91-
$point,
92-
$context = null,
93-
$instance = null,
94-
array $arguments = null
95-
): bool {
96-
return $pointcut->matches($point, $context, $instance, $arguments)
97-
&& $pointcut->getClassFilter()->matches($context);
72+
return $this->pointcutKind;
9873
}
9974
}

0 commit comments

Comments
 (0)