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