ClassParser   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 264
Duplicated Lines 3.41 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 28
c 0
b 0
f 0
lcom 1
cbo 12
dl 9
loc 264
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getToken() 0 4 1
B getDefinitionFromTokens() 9 113 7
A addAncestralAssertions() 0 39 4
B getParent() 0 21 8
B getInterfaces() 0 26 8

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\Parser\ClassParser
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\Parser;
22
23
use AppserverIo\Doppelgaenger\Entities\Definitions\ClassDefinition;
24
use AppserverIo\Doppelgaenger\Entities\Definitions\Structure;
25
use AppserverIo\Doppelgaenger\Entities\Introduction;
26
use AppserverIo\Doppelgaenger\Entities\Lists\IntroductionList;
27
use AppserverIo\Doppelgaenger\Exceptions\GeneratorException;
28
use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Introduce;
29
use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Invariant;
30
31
/**
32
 * This class implements the StructureParserInterface for class structures
33
 *
34
 * @author    Bernhard Wick <[email protected]>
35
 * @copyright 2015 TechDivision GmbH - <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/appserver-io/doppelgaenger
38
 * @link      http://www.appserver.io/
39
 */
40
class ClassParser extends AbstractStructureParser
41
{
42
43
    /**
44
     * We want to be able to parse properties
45
     */
46
    use PropertyParserTrait;
47
48
    /**
49
     * Token representing the structure this parser is used for
50
     *
51
     * @var integer TOKEN
52
     */
53
    const TOKEN = T_CLASS;
54
55
    /**
56
     * Will return the token representing the structure the parser is used for e.g. T_CLASS
57
     *
58
     * @return integer
59
     */
60
    public function getToken()
61
    {
62
        return self::TOKEN;
63
    }
64
65
    /**
66
     * Returns a ClassDefinition from a token array.
67
     *
68
     * This method will use a set of other methods to parse a token array and retrieve any
69
     * possible information from it. This information will be entered into a ClassDefinition object.
70
     *
71
     * @param array   $tokens       The token array containing structure tokens
72
     * @param boolean $getRecursive Do we have to get the ancestral conditions as well?
73
     *
74
     * @return \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface
75
     *
76
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
77
     */
78
    protected function getDefinitionFromTokens($tokens, $getRecursive = true)
79
    {
80
        // First of all we need a new ClassDefinition to fill
81 View Code Duplication
        if (is_null($this->currentDefinition)) {
82
            $this->currentDefinition = new ClassDefinition();
83
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
84
        } elseif (!$this->currentDefinition instanceof ClassDefinition) {
85
            throw new GeneratorException(sprintf(
86
                'The structure definition %s does not seem to be a class definition.',
87
                $this->currentDefinition->getQualifiedName()
88
            ));
89
        }
90
91
        // Save the path of the original definition for later use
92
        $this->currentDefinition->setPath($this->file);
93
94
        // File based namespaces do not make much sense, so hand it over here.
95
        $this->currentDefinition->setNamespace($this->getNamespace());
96
        $this->currentDefinition->setName($this->getName($tokens));
97
        $this->currentDefinition->setUsedStructures($this->getUsedStructures());
98
99
        // For our next step we would like to get the doc comment (if any)
100
        $this->currentDefinition->setDocBlock($this->getDocBlock($tokens, self::TOKEN));
101
102
        // Get start and end line
103
        $this->currentDefinition->setStartLine($this->getStartLine($tokens));
104
        $this->currentDefinition->setEndLine($this->getEndLine($tokens));
105
106
        // Lets get the attributes the class might have
107
        $this->currentDefinition->setAttributeDefinitions($this->getAttributes(
108
            $tokens
109
        ));
110
111
        // So we got our docBlock, now we can parse the invariant annotations from it
112
        $annotationParser = new AnnotationParser($this->file, $this->config, $this->tokens, $this->currentDefinition);
113
        $invariantConditions = $annotationParser->getConditions(
114
            $this->currentDefinition->getDocBlock(),
115
            Invariant::ANNOTATION
116
        );
117
        if (!is_bool($invariantConditions)) {
118
            $this->currentDefinition->setInvariantConditions($invariantConditions);
119
        }
120
121
        // we would be also interested in introductions
122
        $introductions = new IntroductionList();
123
        $introductionAnnotations = $annotationParser->getAnnotationsByType(
124
            $this->currentDefinition->getDocBlock(),
125
            Introduce::ANNOTATION
126
        );
127
        foreach ($introductionAnnotations as $introductionAnnotation) {
128
            $introduction = new Introduction();
129
            $introduction->setTarget($this->currentDefinition->getQualifiedName());
130
            $introduction->setImplementation($introductionAnnotation->values['implementation']);
131
            $introduction->setInterface($introductionAnnotation->values['interface']);
132
133
            $introductions->add($introduction);
134
        }
135
136
        $this->currentDefinition->setIntroductions($introductions);
137
138
        // Get the class identity
139
        $this->currentDefinition->setIsFinal($this->hasSignatureToken($this->tokens, T_FINAL, self::TOKEN));
140
        $this->currentDefinition->setIsAbstract($this->hasSignatureToken($this->tokens, T_ABSTRACT, self::TOKEN));
141
142
        // Lets check if there is any inheritance, or if we implement any interfaces
143
        $this->currentDefinition->setExtends(trim(
144
            $this->resolveUsedNamespace(
145
                $this->currentDefinition,
146
                $this->getParent($tokens)
147
            ),
148
            '\\'
149
        ));
150
        // Get all the interfaces we have
151
        $this->currentDefinition->setImplements($this->getInterfaces($this->currentDefinition));
0 ignored issues
show
Compatibility introduced by
$this->currentDefinition of type object<AppserverIo\Doppe...ureDefinitionInterface> is not a sub-type of object<AppserverIo\Doppe...itions\ClassDefinition>. It seems like you assume a concrete implementation of the interface AppserverIo\Doppelgaenge...tureDefinitionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface AppserverIo\Doppelgaenge...tureDefinitionInterface as the method setImplements() does only exist in the following implementations of said interface: AppserverIo\Doppelgaenge...itions\AspectDefinition, AppserverIo\Doppelgaenge...nitions\ClassDefinition.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
152
153
        // Get all class constants
154
        $this->currentDefinition->setConstants($this->getConstants($tokens));
0 ignored issues
show
Unused Code introduced by
The call to ClassParser::getConstants() has too many arguments starting with $tokens.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
155
156
        // Only thing still missing are the methods, so ramp up our FunctionParser
157
        $functionParser = new FunctionParser(
158
            $this->file,
159
            $this->config,
160
            $this->structureDefinitionHierarchy,
161
            $this->structureMap,
162
            $this->currentDefinition,
163
            $this->tokens
164
        );
165
166
        $functionDefinitions = $functionParser->getDefinitionListFromTokens(
167
            $tokens,
168
            $getRecursive
169
        );
170
        if ($functionDefinitions !== false) {
171
            $this->currentDefinition->setFunctionDefinitions($functionDefinitions);
172
        }
173
174
        // If we have to parse the definition in a recursive manner, we have to get the parent invariants
175
        if ($getRecursive === true) {
176
            // Add all the assertions we might get from ancestral dependencies
177
            $this->addAncestralAssertions($this->currentDefinition);
178
        }
179
180
        // Lets get the attributes the class might have
181
        $this->currentDefinition->setAttributeDefinitions($this->getAttributes(
182
            $tokens,
183
            $this->currentDefinition->getInvariants()
184
        ));
185
186
        // Before exiting we will add the entry to the current structure definition hierarchy
187
        $this->structureDefinitionHierarchy->insert($this->currentDefinition);
188
189
        return $this->currentDefinition;
190
    }
191
192
    /**
193
     * This method will add all assertions any ancestral structures (parent classes, implemented interfaces) might have
194
     * to the passed class definition.
195
     *
196
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\ClassDefinition $classDefinition The class definition we have to
197
     *                                                                                add the assertions to
198
     *
199
     * @return null
200
     */
201
    protected function addAncestralAssertions(ClassDefinition $classDefinition)
202
    {
203
        $dependencies = $classDefinition->getDependencies();
204
        foreach ($dependencies as $dependency) {
205
            // freshly set the dependency definition to avoid side effects
206
            $dependencyDefinition = null;
0 ignored issues
show
Unused Code introduced by
$dependencyDefinition is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
207
208
            $fileEntry = $this->structureMap->getEntry($dependency);
209
            if (!$fileEntry instanceof Structure) {
210
                // Continue, don't fail as we might have dependencies which are not under Doppelgaenger surveillance
211
                continue;
212
            }
213
214
            // Get the needed parser
215
            $structureParserFactory = new StructureParserFactory();
216
            $parser = $structureParserFactory->getInstance(
217
                $fileEntry->getType(),
218
                $fileEntry->getPath(),
219
                $this->config,
220
                $this->structureMap,
221
                $this->structureDefinitionHierarchy
222
            );
223
224
            // Get the definition
225
            $dependencyDefinition = $parser->getDefinition(
226
                $dependency,
227
                true
228
            );
229
230
            // Only classes and traits have invariants
231
            if ($fileEntry->getType() === 'class') {
232
                $classDefinition->setAncestralInvariants($dependencyDefinition->getInvariants(true));
233
            }
234
235
            // Finally add the dependency definition to our structure definition hierarchy to avoid
236
            // redundant parsing
237
            $this->structureDefinitionHierarchy->insert($dependencyDefinition);
238
        }
239
    }
240
241
    /**
242
     * Will find the parent class we have (if any). Will return an empty string if there is none.
243
     *
244
     * @param array $tokens Array of tokens for this class
245
     *
246
     * @return string
247
     */
248
    protected function getParent(array $tokens)
249
    {
250
        // Check the tokens
251
        $className = '';
252
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
253
            // If we got the class name
254
            if ($tokens[$i][0] === T_EXTENDS) {
255
                for ($j = $i + 1; $j < count($tokens); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
256
                    if ($tokens[$j] === '{' || $tokens[$j][0] === T_CURLY_OPEN || $tokens[$j][0] === T_IMPLEMENTS) {
257
                        return $className;
258
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
259
                    } elseif ($tokens[$j][0] === T_STRING) {
260
                        $className .= $tokens[$j][1];
261
                    }
262
                }
263
            }
264
        }
265
266
        // Return what we did or did not found
267
        return $className;
268
    }
269
270
    /**
271
     * Will return an array containing all interfaces this class implements
272
     *
273
     * @param ClassDefinition $classDefinition Reference of class definition so we can resolve the namespaces
274
     *
275
     * @return array
276
     */
277
    protected function getInterfaces(ClassDefinition & $classDefinition)
278
    {
279
        // Check the tokens
280
        $interfaces = array();
281
        for ($i = 0; $i < $this->tokenCount; $i++) {
282
            // If we got the class name
283
            if ($this->tokens[$i][0] === T_IMPLEMENTS) {
284
                for ($j = $i + 1; $j < $this->tokenCount; $j++) {
285
                    if ($this->tokens[$j] === '{' || $this->tokens[$j][0] === T_CURLY_OPEN ||
286
                        $this->tokens[$j][0] === T_EXTENDS
287
                    ) {
288
                        return $interfaces;
289
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
290
                    } elseif ($this->tokens[$j][0] === T_STRING) {
291
                        $interfaces[] = $this->resolveUsedNamespace(
292
                            $classDefinition,
293
                            $this->tokens[$j][1]
294
                        );
295
                    }
296
                }
297
            }
298
        }
299
300
        // Return what we did or did not found
301
        return $interfaces;
302
    }
303
}
304