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