Completed
Push — master ( 10160b...eee693 )
by Alexander
06:23 queued 04:47
created

AdviceMatcher::getAdvicesForClass()   D

Complexity

Conditions 9
Paths 40

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 9.1317

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 15
cts 17
cp 0.8824
rs 4.909
c 0
b 0
f 0
cc 9
eloc 17
nc 40
nop 2
crap 9.1317
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Core;
13
14
use Go\Aop;
15
use Go\Aop\Support\NamespacedReflectionFunction;
16
use Go\ParserReflection\ReflectionFileNamespace;
17
use ReflectionClass;
18
use ReflectionMethod;
19
use ReflectionProperty;
20
21
/**
22
 * Advice matcher returns the list of advices for the specific point of code
23
 */
24
class AdviceMatcher
25
{
26
    /**
27
     * Loader of aspects
28
     *
29
     * @var AspectLoader
30
     */
31
    protected $loader;
32
33
    /**
34
     * Flag to enable/disable support of global function interception
35
     *
36
     * @var bool
37
     */
38
    private $isInterceptFunctions;
39
40
    /**
41
     * Constructor
42
     *
43
     * @param AspectLoader $loader Instance of aspect loader
44
     * @param bool $isInterceptFunctions Optional flag to enable function interception
45
     */
46 4
    public function __construct(AspectLoader $loader, bool $isInterceptFunctions = false)
47
    {
48 4
        $this->loader = $loader;
49
50 4
        $this->isInterceptFunctions = $isInterceptFunctions;
51 4
    }
52
53
    /**
54
     * Returns list of function advices for namespace
55
     *
56
     * @param ReflectionFileNamespace $namespace
57
     * @param array|Aop\Advisor[] $advisors List of advisor to match
58
     *
59
     * @return array
60
     */
61 7
    public function getAdvicesForFunctions(ReflectionFileNamespace $namespace, array $advisors): array
62
    {
63 7
        if (!$this->isInterceptFunctions) {
64 7
            return [];
65
        }
66
67
        $advices = [];
68
69
        foreach ($advisors as $advisorId => $advisor) {
70
            if ($advisor instanceof Aop\PointcutAdvisor) {
71
                $pointcut = $advisor->getPointcut();
72
                $isFunctionAdvisor = $pointcut->getKind() & Aop\PointFilter::KIND_FUNCTION;
73
                if ($isFunctionAdvisor && $pointcut->getClassFilter()->matches($namespace)) {
74
                    $advices[] = $this->getFunctionAdvicesFromAdvisor($namespace, $advisor, $advisorId, $pointcut);
75
                }
76
            }
77
        }
78
79
        if (count($advices) > 0) {
80
            $advices = array_merge_recursive(...$advices);
81
        }
82
83
        return $advices;
84
    }
85
86
    /**
87
     * Return list of advices for class
88
     *
89
     * @param ReflectionClass $class Class to advise
90
     * @param array|Aop\Advisor[] $advisors List of advisor to match
91
     *
92
     * @return array|Aop\Advice[] List of advices for class
93
     */
94 3
    public function getAdvicesForClass(ReflectionClass $class, array $advisors): array
95
    {
96 3
        $classAdvices = [];
97 3
        $parentClass  = $class->getParentClass();
98
99 3
        $originalClass = $class;
100 3
        if ($parentClass && strpos($parentClass->name, AspectContainer::AOP_PROXIED_SUFFIX) !== false) {
101
            $originalClass = $parentClass;
102
        }
103
104 3
        foreach ($advisors as $advisorId => $advisor) {
105 2
            if ($advisor instanceof Aop\PointcutAdvisor) {
106 2
                $pointcut = $advisor->getPointcut();
107 2
                if ($pointcut->getClassFilter()->matches($class)) {
108 2
                    $classAdvices[] = $this->getAdvicesFromAdvisor($originalClass, $advisor, $advisorId, $pointcut);
109
                }
110
            }
111
112 2
            if ($advisor instanceof Aop\IntroductionAdvisor) {
113
                if ($advisor->getClassFilter()->matches($class)) {
114 2
                    $classAdvices[] = $this->getIntroductionFromAdvisor($originalClass, $advisor, $advisorId);
115
                }
116
            }
117
        }
118 3
        if (count($classAdvices) > 0) {
119 2
            $classAdvices = array_merge_recursive(...$classAdvices);
120
        }
121
122 3
        return $classAdvices;
123
    }
124
125
    /**
126
     * Returns list of advices from advisor and point filter
127
     *
128
     * @param ReflectionClass $class Class to inject advices
129
     * @param Aop\PointcutAdvisor $advisor Advisor for class
130
     * @param string $advisorId Identifier of advisor
131
     * @param Aop\PointFilter $filter Filter for points
132
     *
133
     * @return array
134
     */
135 2
    private function getAdvicesFromAdvisor(
136
        ReflectionClass $class,
137
        Aop\PointcutAdvisor $advisor,
138
        string $advisorId,
139
        Aop\PointFilter $filter
140
    ): array {
141 2
        $classAdvices = [];
142 2
        $filterKind   = $filter->getKind();
143
144
        // Check class only for class filters
145 2
        if ($filterKind & Aop\PointFilter::KIND_CLASS) {
146
            if ($filter->matches($class)) {
147
                // Dynamic initialization
148
                if ($filterKind & Aop\PointFilter::KIND_INIT) {
149
                    $classAdvices[AspectContainer::INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
150
                }
151
                // Static initalization
152
                if ($filterKind & Aop\PointFilter::KIND_STATIC_INIT) {
153
                    $classAdvices[AspectContainer::STATIC_INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
154
                }
155
            }
156
        }
157
158
        // Check methods in class only for method filters
159 2
        if ($filterKind & Aop\PointFilter::KIND_METHOD) {
160 1
            $mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED;
161 1
            foreach ($class->getMethods($mask) as $method) {
162
                // abstract and parent final methods could not be woven
163 1
                $isParentFinalMethod = ($method->getDeclaringClass()->name !== $class->name) && $method->isFinal();
164 1
                if ($isParentFinalMethod || $method->isAbstract()) {
165
                    continue;
166
                }
167
168 1
                if ($filter->matches($method, $class)) {
169 1
                    $prefix = $method->isStatic() ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX;
170 1
                    $classAdvices[$prefix][$method->name][$advisorId] = $advisor->getAdvice();
171
                }
172
            }
173
        }
174
175
        // Check properties in class only for property filters
176 2
        if ($filterKind & Aop\PointFilter::KIND_PROPERTY) {
177 1
            $mask = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED;
178 1
            foreach ($class->getProperties($mask) as $property) {
179 1
                if ($filter->matches($property, $class) && !$property->isStatic()) {
180 1
                    $classAdvices[AspectContainer::PROPERTY_PREFIX][$property->name][$advisorId] = $advisor->getAdvice();
181
                }
182
            }
183
        }
184
185 2
        return $classAdvices;
186
    }
187
188
    /**
189
     * Returns list of introduction advices from advisor
190
     *
191
     * @param ReflectionClass $class Class to inject advices
192
     * @param Aop\IntroductionAdvisor $advisor Advisor for class
193
     * @param string $advisorId Identifier of advisor
194
     *
195
     * @return array
196
     */
197
    private function getIntroductionFromAdvisor(
198
        ReflectionClass $class,
199
        Aop\IntroductionAdvisor $advisor,
200
        string $advisorId
201
    ): array {
202
        $classAdvices = [];
203
        // Do not make introduction for traits
204
        if ($class->isTrait()) {
205
            return $classAdvices;
206
        }
207
208
        $advice = $advisor->getAdvice();
209
210
        $classAdvices[AspectContainer::INTRODUCTION_TRAIT_PREFIX][$advisorId] = $advice;
211
212
        return $classAdvices;
213
    }
214
215
    /**
216
     * Returns list of function advices for specific namespace
217
     *
218
     * @param ReflectionFileNamespace $namespace
219
     * @param Aop\PointcutAdvisor $advisor Advisor for class
220
     * @param string $advisorId Identifier of advisor
221
     * @param Aop\PointFilter $pointcut Filter for points
222
     *
223
     * @return array
224
     */
225
    private function getFunctionAdvicesFromAdvisor(
226
        ReflectionFileNamespace $namespace,
227
        Aop\PointcutAdvisor $advisor,
228
        string $advisorId,
229
        Aop\PointFilter $pointcut
230
    ): array {
231
        $functions = [];
232
        $advices   = [];
233
234
        $listOfGlobalFunctions = get_defined_functions();
235
        foreach ($listOfGlobalFunctions['internal'] as $functionName) {
236
            $functions[$functionName] = new NamespacedReflectionFunction($functionName, $namespace->getName());
237
        }
238
239
        foreach ($functions as $functionName => $function) {
240
            if ($pointcut->matches($function, $namespace)) {
241
                $advices[AspectContainer::FUNCTION_PREFIX][$functionName][$advisorId] = $advisor->getAdvice();
242
            }
243
        }
244
245
        return $advices;
246
    }
247
}
248