AspectRegister::register()   C
last analyzed

Complexity

Conditions 11
Paths 35

Size

Total Lines 98

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 98
rs 5.8968
c 0
b 0
f 0
cc 11
nc 35
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\AspectRegister
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Bernhard Wick <[email protected]>
15
 * @copyright 2015 TechDivision GmbH - <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/doppelgaenger
18
 * @link      http://www.appserver.io/
19
 */
20
21
namespace AppserverIo\Doppelgaenger;
22
23
use AppserverIo\Doppelgaenger\Entities\Definitions\Advice;
24
use AppserverIo\Doppelgaenger\Entities\Definitions\Aspect;
25
use AppserverIo\Doppelgaenger\Entities\Definitions\AspectDefinition;
26
use AppserverIo\Doppelgaenger\Entities\Lists\AbstractTypedList;
27
use AppserverIo\Doppelgaenger\Entities\Lists\TypedList;
28
use AppserverIo\Doppelgaenger\Entities\PointcutExpression;
29
use AppserverIo\Doppelgaenger\Entities\Pointcuts\PointcutFactory;
30
use AppserverIo\Doppelgaenger\Entities\Definitions\Pointcut as PointcutDefinition;
31
use AppserverIo\Doppelgaenger\Entities\Pointcuts\PointcutPointcut;
32
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\After;
33
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\AfterReturning;
34
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\AfterThrowing;
35
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\Around;
36
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\Before;
37
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Pointcut;
38
use Herrera\Annotations\Convert\ToArray;
39
use Herrera\Annotations\Tokenizer;
40
use Herrera\Annotations\Tokens;
41
42
/**
43
 * Class which knows about registered aspects to allow for checks of methods against all given pointcuts
44
 *
45
 * @author    Bernhard Wick <[email protected]>
46
 * @copyright 2015 TechDivision GmbH - <[email protected]>
47
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
48
 * @link      https://github.com/appserver-io/doppelgaenger
49
 * @link      http://www.appserver.io/
50
 */
51
class AspectRegister extends AbstractTypedList
52
{
53
54
    /**
55
     * Default constructor
56
     */
57
    public function __construct()
58
    {
59
        parent::__construct();
60
61
        $this->itemType = '\AppserverIo\Doppelgaenger\Entities\Definitions\Aspect';
62
        $this->defaultOffset = 'qualifiedName';
63
    }
64
65
    /**
66
     * Look up a certain advice based on a glob-like expression optionally containing aspect name and advice name
67
     *
68
     * @param string $adviceExpression Expression defining the search term
69
     *
70
     * @return array<\AppserverIo\Doppelgaenger\Entities\Definitions\Advice>
71
     */
72 View Code Duplication
    public function lookupAdvice($adviceExpression)
73
    {
74
        // clean the expression
75
        $adviceExpression = trim(ltrim(rtrim($adviceExpression, '()'), '\\'));
76
77
        // if there is an aspect name within the expression we have to filter our search range and cut the expression
78
        $container = $this->container;
79
        if (strpos($adviceExpression, '->')) {
80
            $aspectExpression = strstr($adviceExpression, '->', true);
81
            $container = $this->lookupAspects($aspectExpression);
82
            $adviceExpression = str_replace('->', '', strstr($adviceExpression, '->'));
83
        }
84
85
        $matches = array();
86
        foreach ($container as $aspect) {
87
            $matches = array_merge($matches, $this->lookupEntries($aspect->getAdvices(), $adviceExpression));
88
        }
89
90
        return $matches;
91
    }
92
93
    /**
94
     * Will narrow down the choice of aspects to make when looking up pointcuts or advices.
95
     * Just pass the expression to look up any of both
96
     *
97
     * @param string $expression Expression defining the search term
98
     *
99
     * @return array<\AppserverIo\Doppelgaenger\Entities\Definitions\Aspect>
100
     */
101
    public function lookupAspects($expression)
102
    {
103
        return $this->lookupEntries($this->container, $expression);
104
    }
105
106
    /**
107
     * Look up certain entities in a container based on their qualified name and a glob-like expression
108
     *
109
     * @param array|\Traversable $container  Traversable container we will look in
110
     * @param string             $expression Expression defining the search term
111
     *
112
     * @return array
113
     */
114
    protected function lookupEntries($container, $expression)
115
    {
116
117
        // clean the expression
118
        $expression = trim(ltrim(rtrim($expression, '()'), '\\'));
119
120
        // if we got the complete name of the aspect we can return it alone
121
        if ($this->entryExists($expression)) {
122
            return array($this->get($expression));
123
        }
124
125
        // as it seems we got something else we have to get all regex about
126
        $matches = array();
127
        foreach ($container as $entry) {
128
            if (fnmatch($expression, $entry->getQualifiedName())) {
129
                $matches[] = $entry;
130
            }
131
        }
132
133
        return $matches;
134
    }
135
136
    /**
137
     * Look up a certain advice based on a glob-like expression optionally containing aspect name and pointcut name
138
     *
139
     * @param string $pointcutExpression Expression defining the search term
140
     *
141
     * @return array<\AppserverIo\Doppelgaenger\Entities\Definitions\Pointcut>
142
     */
143 View Code Duplication
    public function lookupPointcuts($pointcutExpression)
144
    {
145
        // clean the expression
146
        $pointcutExpression = trim(ltrim(rtrim($pointcutExpression, '()'), '\\'));
147
148
        // if there is an aspect name within the expression we have to filter our search range and cut the expression
149
        $container = $this->container;
150
        if (strpos($pointcutExpression, '->')) {
151
            $aspectExpression = strstr($pointcutExpression, '->', true);
152
            $container = $this->lookupAspects($aspectExpression);
153
            $pointcutExpression = str_replace('->', '', strstr($pointcutExpression, '->'));
154
        }
155
156
        $matches = array();
157
        foreach ($container as $aspect) {
158
            $matches = array_merge($matches, $this->lookupEntries($aspect->getPointcuts(), $pointcutExpression));
159
        }
160
161
        return $matches;
162
    }
163
164
    /**
165
     * Will register a complete aspect to the AspectRegister.
166
     * This include its advices and pointcuts which can be looked up from this point on
167
     *
168
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\AspectDefinition $aspectDefinition Structure to register as an aspect
169
     *
170
     * @return null
171
     */
172
    public function register(AspectDefinition $aspectDefinition)
173
    {
174
175
        // create the new aspect and fill it with things we already know
176
        $aspect = new Aspect();
177
        $aspect->setName($aspectDefinition->getName());
178
        $aspect->setNamespace($aspectDefinition->getNamespace());
179
180
        // prepare the tokenizer we will need for further processing
181
        $needles = array(
182
            AfterReturning::ANNOTATION,
183
            AfterThrowing::ANNOTATION,
184
            After::ANNOTATION,
185
            Around::ANNOTATION,
186
            Before::ANNOTATION
187
        );
188
        $tokenizer = new Tokenizer();
189
        $tokenizer->ignore(
190
            array(
191
                'param',
192
                'return',
193
                'throws'
194
            )
195
        );
196
197
        // iterate the functions and filter out the ones used as advices
198
        $scheduledAdviceDefinitions = array();
199
        foreach ($aspectDefinition->getFunctionDefinitions() as $functionDefinition) {
0 ignored issues
show
Bug introduced by
The expression $aspectDefinition->getFunctionDefinitions() of type null|object<AppserverIo\...FunctionDefinitionList> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
200
            $foundNeedle = false;
201
            foreach ($needles as $needle) {
202
                // create the advice
203
                if (strpos($functionDefinition->getDocBlock(), '@' . $needle) !== false) {
204
                    $foundNeedle = true;
205
                    $scheduledAdviceDefinitions[$needle][] = $functionDefinition;
206
207
                    break;
208
                }
209
            }
210
211
            // create the pointcut
212
            if (!$foundNeedle && strpos($functionDefinition->getDocBlock(), '@' . Pointcut::ANNOTATION) !== false) {
213
                $pointcut = new PointcutDefinition();
214
                $pointcut->setName($functionDefinition->getName());
215
216
                $tokens = new Tokens($tokenizer->parse($functionDefinition->getDocBlock()));
217
218
                // convert to array and run it through our advice factory
219
                $toArray = new ToArray();
220
                $annotations = $toArray->convert($tokens);
221
222
                // create the entities for the join-points and advices the pointcut describes
223
                $pointcut->setPointcutExpression(new PointcutExpression(array_pop(array_pop($annotations)->values)));
224
                $aspect->getPointcuts()->add($pointcut);
225
            }
226
        }
227
        $this->add($aspect);
228
229
        // do the pointcut lookups where we will need the pointcut factory for later use
230
        $pointcutFactory = new PointcutFactory();
231
        foreach ($scheduledAdviceDefinitions as $codeHook => $hookedAdviceDefinitions) {
232
            foreach ($hookedAdviceDefinitions as $scheduledAdviceDefinition) {
233
                // create our advice
234
                $advice = new Advice();
235
                $advice->setAspectName($aspectDefinition->getQualifiedName());
236
                $advice->setName($scheduledAdviceDefinition->getName());
237
                $advice->setCodeHook((string) $codeHook);
238
239
                $tokens = new Tokens($tokenizer->parse($scheduledAdviceDefinition->getDocBlock()));
240
241
                // convert to array and run it through our advice factory
242
                $toArray = new ToArray();
243
                $annotations = $toArray->convert($tokens);
244
245
                // create the entities for the join-points and advices the pointcut describes
246
                foreach ($annotations as $annotation) {
247
                    $pointcut = $pointcutFactory->getInstance(array_pop($annotation->values));
248
                    if ($pointcut instanceof PointcutPointcut) {
249
                        // get the referenced pointcuts for the split parts of the expression
250
                        $expressionParts = explode(PointcutPointcut::EXPRESSION_CONNECTOR, $pointcut->getExpression());
251
252
                        // lookup all the referenced pointcuts
253
                        $referencedPointcuts = array();
254
                        foreach ($expressionParts as $expressionPart) {
255
                            $referencedPointcuts = array_merge($referencedPointcuts, $this->lookupPointcuts($expressionPart));
256
                        }
257
258
                        $pointcut->setReferencedPointcuts($referencedPointcuts);
259
                    }
260
261
                    $advice->getPointcuts()->add($pointcut);
262
                }
263
264
                $aspect->getAdvices()->add($advice);
265
            }
266
        }
267
268
        $this->set($aspectDefinition->getQualifiedName(), $aspect);
269
    }
270
}
271