Completed
Pull Request — master (#463)
by Alexander
30:17 queued 05:15
created

AdviceMatcher::getAdvicesForClass()   B

Complexity

Conditions 9
Paths 40

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.4453

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 14
cts 17
cp 0.8235
rs 8.0555
c 0
b 0
f 0
cc 9
nc 40
nop 2
crap 9.4453
1
<?php
2
3
declare(strict_types = 1);
4
/*
5
 * Go! AOP framework
6
 *
7
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
8
 *
9
 * This source file is subject to the license that is bundled
10
 * with this source code in the file LICENSE.
11
 */
12
13
namespace Go\Core;
14
15
use Go\Aop;
16
use Go\Aop\IntroductionAdvisor;
17
use Go\Aop\PointcutAdvisor;
18
use Go\Aop\PointFilter;
19
use Go\Aop\Support\NamespacedReflectionFunction;
20
use Go\ParserReflection\ReflectionFileNamespace;
21
use ReflectionClass;
22
use ReflectionMethod;
23
use ReflectionProperty;
24
25
use function count;
26
27
/**
28
 * Advice matcher returns the list of advices for the specific point of code
29
 */
30
class AdviceMatcher implements AdviceMatcherInterface
31
{
32
    /**
33
     * Flag to enable/disable support of global function interception
34
     */
35
    private bool $isInterceptFunctions = false;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
36
37
    /**
38
     * Constructor
39 5
     *
40
     * @param bool $isInterceptFunctions Optional flag to enable function interception
41 5
     */
42 5
    public function __construct(bool $isInterceptFunctions = false)
43
    {
44
        $this->isInterceptFunctions = $isInterceptFunctions;
45
    }
46
47
    /**
48
     * Returns list of function advices for namespace
49
     *
50
     * @param Aop\Advisor[] $advisors List of advisor to match
51 8
     *
52
     * @return Aop\Advice[] List of advices for class
53 8
     */
54 8
    public function getAdvicesForFunctions(ReflectionFileNamespace $namespace, array $advisors): array
55
    {
56
        if (!$this->isInterceptFunctions) {
57
            return [];
58
        }
59
60
        $advices = [];
61
62
        foreach ($advisors as $advisorId => $advisor) {
63
            if ($advisor instanceof PointcutAdvisor) {
64
                $pointcut = $advisor->getPointcut();
65
                $isFunctionAdvisor = $pointcut->getKind() & PointFilter::KIND_FUNCTION;
66
                if ($isFunctionAdvisor && $pointcut->getClassFilter()->matches($namespace)) {
67
                    $advices[] = $this->getFunctionAdvicesFromAdvisor($namespace, $advisor, $advisorId, $pointcut);
68
                }
69
            }
70
        }
71
72
        if (count($advices) > 0) {
73
            $advices = array_merge_recursive(...$advices);
74
        }
75
76
        return $advices;
77
    }
78
79
    /**
80
     * Return list of advices for class
81
     *
82
     * @param array|Aop\Advisor[] $advisors List of advisor to match
83 4
     *
84
     * @return Aop\Advice[][] List of advices for class
85 4
     */
86 4
    public function getAdvicesForClass(ReflectionClass $class, array $advisors): array
87
    {
88 4
        $classAdvices = [];
89 4
        $parentClass  = $class->getParentClass();
90
91
        $originalClass = $class;
92
        if ($parentClass && strpos($parentClass->name, AspectContainer::AOP_PROXIED_SUFFIX) !== false) {
93 4
            $originalClass = $parentClass;
94 3
        }
95 3
96 3
        foreach ($advisors as $advisorId => $advisor) {
97 3
            if ($advisor instanceof PointcutAdvisor) {
98
                $pointcut = $advisor->getPointcut();
99
                if ($pointcut->getClassFilter()->matches($class)) {
100
                    $classAdvices[] = $this->getAdvicesFromAdvisor($originalClass, $advisor, $advisorId, $pointcut);
101 3
                }
102
            }
103
104
            if ($advisor instanceof IntroductionAdvisor) {
105
                if ($advisor->getClassFilter()->matches($class)) {
106
                    $classAdvices[] = $this->getIntroductionFromAdvisor($originalClass, $advisor, $advisorId);
107 4
                }
108 3
            }
109
        }
110
        if (count($classAdvices) > 0) {
111 4
            $classAdvices = array_merge_recursive(...$classAdvices);
112
        }
113
114
        return $classAdvices;
115
    }
116
117 3
    /**
118
     * Returns list of advices from advisor and point filter
119
     */
120
    private function getAdvicesFromAdvisor(
121
        ReflectionClass $class,
122
        PointcutAdvisor $advisor,
123 3
        string $advisorId,
124 3
        PointFilter $filter
125
    ): array {
126
        $classAdvices = [];
127 3
        $filterKind   = $filter->getKind();
128 1
129
        // Check class only for class filters
130 1
        if (($filterKind & PointFilter::KIND_CLASS) !== 0) {
131 1
            if ($filter->matches($class)) {
132
                // Dynamic initialization
133
                if (($filterKind & PointFilter::KIND_INIT) !== 0) {
134 1
                    $classAdvices[AspectContainer::INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
135 1
                }
136
                // Static initalization
137
                if (($filterKind & PointFilter::KIND_STATIC_INIT) !== 0) {
138
                    $classAdvices[AspectContainer::STATIC_INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
139
                }
140
            }
141 3
        }
142 2
143 2
        // Check methods in class only for method filters
144
        if (($filterKind & PointFilter::KIND_METHOD) !== 0) {
145 2
            $mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED;
146 2
            foreach ($class->getMethods($mask) as $method) {
147 1
                // abstract and parent final methods could not be woven
148
                $isParentFinalMethod = ($method->getDeclaringClass()->name !== $class->name) && $method->isFinal();
149
                if ($isParentFinalMethod || $method->isAbstract()) {
150 2
                    continue;
151 2
                }
152 2
153
                if ($filter->matches($method, $class)) {
154
                    $prefix = $method->isStatic() ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX;
155
                    $classAdvices[$prefix][$method->name][$advisorId] = $advisor->getAdvice();
156
                }
157
            }
158 3
        }
159 2
160 2
        // Check properties in class only for property filters
161 2
        if (($filterKind & PointFilter::KIND_PROPERTY) !== 0) {
162 2
            $mask = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;
163
            foreach ($class->getProperties($mask) as $property) {
164
                if ($filter->matches($property, $class) && !$property->isStatic()) {
165
                    $classAdvices[AspectContainer::PROPERTY_PREFIX][$property->name][$advisorId] = $advisor->getAdvice();
166
                }
167 3
            }
168
        }
169
170
        return $classAdvices;
171
    }
172
173
    /**
174
     * Returns list of introduction advices from advisor
175
     */
176
    private function getIntroductionFromAdvisor(
177
        ReflectionClass $class,
178
        IntroductionAdvisor $advisor,
179
        string $advisorId
180
    ): array {
181
        $classAdvices = [];
182
        // Do not make introduction for traits
183
        if ($class->isTrait()) {
184
            return $classAdvices;
185
        }
186
187
        /** @var Aop\IntroductionInfo $introduction */
188
        $introduction    = $advisor->getAdvice();
189
        $introducedTrait = $introduction->getTrait();
190
        if (!empty($introducedTrait)) {
191
            $introducedTrait = '\\' . ltrim($introducedTrait, '\\');
192
193
            $classAdvices[AspectContainer::INTRODUCTION_TRAIT_PREFIX][$advisorId] = $introducedTrait;
194
        }
195
        $introducedInterface = $introduction->getInterface();
196
        if (!empty($introducedInterface)) {
197
            $introducedInterface = '\\' . ltrim($introducedInterface, '\\');
198
199
            $classAdvices[AspectContainer::INTRODUCTION_INTERFACE_PREFIX][$advisorId] = $introducedInterface;
200
        }
201
202
        return $classAdvices;
203
    }
204
205
    /**
206
     * Returns list of function advices for specific namespace
207
     */
208
    private function getFunctionAdvicesFromAdvisor(
209
        ReflectionFileNamespace $namespace,
210
        PointcutAdvisor $advisor,
211
        string $advisorId,
212
        PointFilter $pointcut
213
    ): array {
214
        $functions = [];
215
        $advices   = [];
216
217
        $listOfGlobalFunctions = get_defined_functions();
218
        foreach ($listOfGlobalFunctions['internal'] as $functionName) {
219
            $functions[$functionName] = new NamespacedReflectionFunction($functionName, $namespace->getName());
220
        }
221
222
        foreach ($functions as $functionName => $function) {
223
            if ($pointcut->matches($function, $namespace)) {
224
                $advices[AspectContainer::FUNCTION_PREFIX][$functionName][$advisorId] = $advisor->getAdvice();
225
            }
226
        }
227
228
        return $advices;
229
    }
230
}
231