Completed
Pull Request — master (#395)
by Alexander
04:36 queued 02:33
created

AdviceMatcher   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 48.15%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 7
dl 0
loc 203
ccs 39
cts 81
cp 0.4815
rs 8.2769
c 0
b 0
f 0

6 Methods

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