Completed
Pull Request — 2.x (#349)
by Alexander
02:20
created

AdviceMatcher   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 48.78%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 7
dl 0
loc 235
ccs 40
cts 82
cp 0.4878
rs 8.2769
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C getAdvicesForFunctions() 0 24 7
D getAdvicesForClass() 0 30 9
C getAdvicesFromAdvisor() 0 52 16
B getIntroductionFromAdvisor() 0 28 4
B getFunctionAdvicesFromAdvisor() 0 22 4

How to fix   Complexity   

Complex Class

Complex classes like AdviceMatcher often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AdviceMatcher, and based on these observations, apply Extract Interface, too.

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
        /** @var Aop\IntroductionInfo $introduction */
209
        $introduction    = $advisor->getAdvice();
210
        $introducedTrait = $introduction->getTrait();
211
        if (!empty($introducedTrait)) {
212
            $introducedTrait = '\\' . ltrim($introducedTrait, '\\');
213
214
            $classAdvices[AspectContainer::INTRODUCTION_TRAIT_PREFIX][$advisorId] = $introducedTrait;
215
        }
216
        $introducedInterface = $introduction->getInterface();
217
        if (!empty($introducedInterface)) {
218
            $introducedInterface = '\\' . ltrim($introducedInterface, '\\');
219
220
            $classAdvices[AspectContainer::INTRODUCTION_INTERFACE_PREFIX][$advisorId] = $introducedInterface;
221
        }
222
223
        return $classAdvices;
224
    }
225
226
    /**
227
     * Returns list of function advices for specific namespace
228
     *
229
     * @param ReflectionFileNamespace $namespace
230
     * @param Aop\PointcutAdvisor $advisor Advisor for class
231
     * @param string $advisorId Identifier of advisor
232
     * @param Aop\PointFilter $pointcut Filter for points
233
     *
234
     * @return array
235
     */
236
    private function getFunctionAdvicesFromAdvisor(
237
        ReflectionFileNamespace $namespace,
238
        Aop\PointcutAdvisor $advisor,
239
        string $advisorId,
240
        Aop\PointFilter $pointcut
241
    ): array {
242
        $functions = [];
243
        $advices   = [];
244
245
        $listOfGlobalFunctions = get_defined_functions();
246
        foreach ($listOfGlobalFunctions['internal'] as $functionName) {
247
            $functions[$functionName] = new NamespacedReflectionFunction($functionName, $namespace->getName());
248
        }
249
250
        foreach ($functions as $functionName => $function) {
251
            if ($pointcut->matches($function, $namespace)) {
252
                $advices[AspectContainer::FUNCTION_PREFIX][$functionName][$advisorId] = $advisor->getAdvice();
253
            }
254
        }
255
256
        return $advices;
257
    }
258
}
259