Completed
Push — master ( 36684b...f083c3 )
by Alexander
02:28
created

AdviceMatcher   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 51.95%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 6
dl 0
loc 226
ccs 40
cts 77
cp 0.5195
rs 8.6
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B getAdvicesForFunctions() 0 23 6
C getAdvicesForClass() 0 33 8
C getAdvicesFromAdvisor() 0 52 16
A getIntroductionFromAdvisor() 0 17 2
B getFunctionAdvicesFromAdvisor() 0 22 4
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 = array_merge_recursive(
75
                        $advices,
76
                        $this->getFunctionAdvicesFromAdvisor($namespace, $advisor, $advisorId, $pointcut)
77
                    );
78
                }
79
            }
80
        }
81
82
        return $advices;
83
    }
84
85
    /**
86
     * Return list of advices for class
87
     *
88
     * @param ReflectionClass $class Class to advise
89
     * @param array|Aop\Advisor[] $advisors List of advisor to match
90
     *
91
     * @return array|Aop\Advice[] List of advices for class
92
     */
93 3
    public function getAdvicesForClass(ReflectionClass $class, array $advisors): array
94
    {
95 3
        $classAdvices = [];
96 3
        $parentClass  = $class->getParentClass();
97
98 3
        $originalClass = $class;
99 3
        if ($parentClass && preg_match('/' . AspectContainer::AOP_PROXIED_SUFFIX . '$/', $parentClass->name)) {
100
            $originalClass = $parentClass;
101
        }
102
103 3
        foreach ($advisors as $advisorId => $advisor) {
104 2
            if ($advisor instanceof Aop\PointcutAdvisor) {
105 2
                $pointcut = $advisor->getPointcut();
106 2
                if ($pointcut->getClassFilter()->matches($class)) {
107 2
                    $classAdvices = array_merge_recursive(
108 2
                        $classAdvices,
109 2
                        $this->getAdvicesFromAdvisor($originalClass, $advisor, $advisorId, $pointcut)
110
                    );
111
                }
112
            }
113
114 2
            if ($advisor instanceof Aop\IntroductionAdvisor) {
115
                if ($advisor->getClassFilter()->matches($class)) {
116
                    $classAdvices = array_merge_recursive(
117
                        $classAdvices,
118 2
                        $this->getIntroductionFromAdvisor($originalClass, $advisor, $advisorId)
119
                    );
120
                }
121
            }
122
        }
123
124 3
        return $classAdvices;
125
    }
126
127
    /**
128
     * Returns list of advices from advisor and point filter
129
     *
130
     * @param ReflectionClass $class Class to inject advices
131
     * @param Aop\PointcutAdvisor $advisor Advisor for class
132
     * @param string $advisorId Identifier of advisor
133
     * @param Aop\PointFilter $filter Filter for points
134
     *
135
     * @return array
136
     */
137 2
    private function getAdvicesFromAdvisor(
138
        ReflectionClass $class,
139
        Aop\PointcutAdvisor $advisor,
140
        string $advisorId,
141
        Aop\PointFilter $filter
142
    ): array {
143 2
        $classAdvices = [];
144 2
        $filterKind   = $filter->getKind();
145
146
        // Check class only for class filters
147 2
        if ($filterKind & Aop\PointFilter::KIND_CLASS) {
148
            if ($filter->matches($class)) {
149
                // Dynamic initialization
150
                if ($filterKind & Aop\PointFilter::KIND_INIT) {
151
                    $classAdvices[AspectContainer::INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
152
                }
153
                // Static initalization
154
                if ($filterKind & Aop\PointFilter::KIND_STATIC_INIT) {
155
                    $classAdvices[AspectContainer::STATIC_INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice();
156
                }
157
            }
158
        }
159
160
        // Check methods in class only for method filters
161 2
        if ($filterKind & Aop\PointFilter::KIND_METHOD) {
162 1
            $mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED;
163 1
            foreach ($class->getMethods($mask) as $method) {
164
                // abstract and parent final methods could not be woven
165 1
                $isParentFinalMethod = ($method->getDeclaringClass()->name !== $class->name) && $method->isFinal();
166 1
                if ($isParentFinalMethod || $method->isAbstract()) {
167
                    continue;
168
                }
169
170 1
                if ($filter->matches($method, $class)) {
171 1
                    $prefix = $method->isStatic() ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX;
172 1
                    $classAdvices[$prefix][$method->name][$advisorId] = $advisor->getAdvice();
173
                }
174
            }
175
        }
176
177
        // Check properties in class only for property filters
178 2
        if ($filterKind & Aop\PointFilter::KIND_PROPERTY) {
179 1
            $mask = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED;
180 1
            foreach ($class->getProperties($mask) as $property) {
181 1
                if ($filter->matches($property, $class) && !$property->isStatic()) {
182 1
                    $classAdvices[AspectContainer::PROPERTY_PREFIX][$property->name][$advisorId] = $advisor->getAdvice();
183
                }
184
            }
185
        }
186
187 2
        return $classAdvices;
188
    }
189
190
    /**
191
     * Returns list of introduction advices from advisor
192
     *
193
     * @param ReflectionClass $class Class to inject advices
194
     * @param Aop\IntroductionAdvisor $advisor Advisor for class
195
     * @param string $advisorId Identifier of advisor
196
     *
197
     * @return array
198
     */
199
    private function getIntroductionFromAdvisor(
200
        ReflectionClass $class,
201
        Aop\IntroductionAdvisor $advisor,
202
        string $advisorId
203
    ): array {
204
        $classAdvices = [];
205
        // Do not make introduction for traits
206
        if ($class->isTrait()) {
207
            return $classAdvices;
208
        }
209
210
        $advice = $advisor->getAdvice();
211
212
        $classAdvices[AspectContainer::INTRODUCTION_TRAIT_PREFIX][$advisorId] = $advice;
213
214
        return $classAdvices;
215
    }
216
217
    /**
218
     * Returns list of function advices for specific namespace
219
     *
220
     * @param ReflectionFileNamespace $namespace
221
     * @param Aop\PointcutAdvisor $advisor Advisor for class
222
     * @param string $advisorId Identifier of advisor
223
     * @param Aop\PointFilter $pointcut Filter for points
224
     *
225
     * @return array
226
     */
227
    private function getFunctionAdvicesFromAdvisor(
228
        ReflectionFileNamespace $namespace,
229
        Aop\PointcutAdvisor $advisor,
230
        string $advisorId,
231
        Aop\PointFilter $pointcut
232
    ): array {
233
        $functions = [];
234
        $advices   = [];
235
236
        $listOfGlobalFunctions = get_defined_functions();
237
        foreach ($listOfGlobalFunctions['internal'] as $functionName) {
238
            $functions[$functionName] = new NamespacedReflectionFunction($functionName, $namespace->getName());
239
        }
240
241
        foreach ($functions as $functionName => $function) {
242
            if ($pointcut->matches($function, $namespace)) {
243
                $advices[AspectContainer::FUNCTION_PREFIX][$functionName][$advisorId] = $advisor->getAdvice();
244
            }
245
        }
246
247
        return $advices;
248
    }
249
}
250