Completed
Push — master ( 634f14...5521c0 )
by Sebastian
02:54
created

Php70Features   F

Complexity

Total Complexity 58

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 100%

Importance

Changes 14
Bugs 2 Features 5
Metric Value
wmc 58
c 14
b 2
f 5
lcom 1
cbo 18
dl 0
loc 176
ccs 117
cts 117
cp 1
rs 2.9203

15 Methods

Rating   Name   Duplication   Size   Complexity  
C enterNode() 0 29 11
B detectAndHandleReservedNamesInUse() 0 12 5
C detectAndHandlePhp4Ctor() 0 23 8
A detectAndHandleReservedNames() 0 4 1
A isFunctionLike() 0 6 3
A detectAndHandleReturnTypeDeclaration() 0 6 3
B detectAndHandleClassAliasCallToReservedName() 0 10 5
A detectAndHandleOperatorAdditions() 0 11 4
A handleYieldFrom() 0 6 2
A handleAnonymousClass() 0 6 2
A handleNewAssignmentByRef() 0 6 2
B handleClassName() 0 13 6
A isNameSoftReserved() 0 5 1
A isNameReserved() 0 5 1
A handleDeclare() 0 10 4

How to fix   Complexity   

Complex Class

Complex classes like Php70Features 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 Php70Features, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Php70Features.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained on one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
18
namespace Pvra\Analysers;
19
20
21
use PhpParser\Node;
22
use PhpParser\Node\Expr;
23
use PhpParser\NodeTraverser;
24
use Pvra\AnalyserAwareInterface;
25
use Pvra\Result\Reason;
26
27
/**
28
 * Class Php70Features
29
 *
30
 * Supported detections:
31
 * * Usage of reserved names (including class_alias function)
32
 * * Detection of PHP4 constructors
33
 * * Anon. Classes
34
 * * Return type declarations
35
 * * Detection of yield from <expr>
36
 * * Removal of new assignment by ref ($x =& new foo)
37
 * * Null coalesce operator
38
 * * Spaceship/combined comparison operator
39
 *
40
 * @package Pvra\Analysers
41
 */
42
class Php70Features extends LanguageFeatureAnalyser implements AnalyserAwareInterface
43
{
44
    // move both to be const once 5.6+ is mandatory
45
    private static $reservedNames = ['string', 'int', 'float', 'bool', 'null', 'false', 'true'];
46
    private static $softReservedNames = ['object', 'resource', 'mixed', 'numeric'];
47
48
    /**
49
     * @inheritdoc
50
     */
51 54
    public function enterNode(Node $node)
52
    {
53 54
        if ($node instanceof Node\Stmt\Use_) {
54 6
            $this->detectAndHandleReservedNamesInUse($node);
55
56 6
            return NodeTraverser::DONT_TRAVERSE_CHILDREN;
57 27
        } elseif ($node instanceof Node\Stmt\ClassLike) {
58 38
            if ($node instanceof Node\Stmt\Class_) {
59 38
                if ($node->isAnonymous()) {
60 2
                    $this->handleAnonymousClass($node);
61 1
                }
62 38
                $this->detectAndHandlePhp4Ctor($node);
63 19
            }
64 38
            $this->detectAndHandleReservedNames($node);
65 54
        } elseif ($this->isFunctionLike($node)) {
66 40
            $this->detectAndHandleReturnTypeDeclaration($node);
67 27
        } elseif ($node instanceof Expr\FuncCall) {
68 28
            $this->detectAndHandleClassAliasCallToReservedName($node);
69 36
        } elseif ($node instanceof Expr\YieldFrom) {
70 4
            $this->handleYieldFrom($node);
71 54
        } elseif ($node instanceof Expr\AssignRef && $node->expr instanceof Expr\New_) {
72 4
            $this->handleNewAssignmentByRef($node);
73 2
        } elseif ($node instanceof Node\Stmt\Declare_) {
74 54
            $this->handleDeclare($node);
75
        }
76 54
        $this->detectAndHandleOperatorAdditions($node);
77
78
        return null;
79 6
    }
80
81 6
    private function detectAndHandleReservedNamesInUse(Node\Stmt\Use_ $node)
82 6
    {
83 6
        if ($node->type === Node\Stmt\Use_::TYPE_NORMAL) {
84 6
            foreach ($node->uses as $use) {
85 3
                if ($use->alias === null || $use->alias === $use->name->getLast()) {
86 5
                    $this->handleClassName($use->name->toString(), $use->name->getLine());
87
                } else {
88 3
                    $this->handleClassName($use->alias, $use->getLine());
89 3
                }
90 6
            }
91
        }
92 38
    }
93
94 38
    private function detectAndHandlePhp4Ctor(Node\Stmt\Class_ $cls)
95 8
    {
96 8
        if ($this->mode & self::MODE_DEPRECATION && !$cls->isAnonymous()) {
97
            $name = isset($cls->namespacedName) ? $cls->namespacedName->toString() : $cls->name;
98 8
            $possibleCtorInfo = null;
99 6
            /** @var Node\Stmt\ClassMethod $method */
100 2
            foreach ($cls->getMethods() as $method) {
101 6
                if (strcasecmp($method->name, '__construct') === 0) {
102
                    return; // This will always be treated as ctor. Drop everything else
103 4
                } elseif (strcasecmp($method->name, ltrim($name, '\\')) === 0) {
104 4
                    $possibleCtorInfo = [
105 2
                        Reason::PHP4_CONSTRUCTOR,
106 5
                        $method->getLine(),
107 2
                        null,
108 2
                        ['name' => $method->name],
109 4
                    ];
110 8
                }
111 4
            }
112 2
            if ($possibleCtorInfo !== null) {
113 4
                call_user_func_array([$this->getResult(), 'addLimit'], $possibleCtorInfo);
114 38
            }
115
        }
116 38
    }
117
118 38
    private function detectAndHandleReservedNames(Node\Stmt\ClassLike $cls)
119 38
    {
120
        $this->handleClassName($cls->name, $cls->getLine());
121 54
    }
122
123 27
    private function isFunctionLike(Node $node)
124 54
    {
125 54
        return $node instanceof Node\Stmt\ClassMethod
126
        || $node instanceof Node\Stmt\Function_
127
        || $node instanceof Expr\Closure;
128 40
    }
129
130 40
    private function detectAndHandleReturnTypeDeclaration(Node $node)
131 4
    {
132 2
        if ($this->mode & self::MODE_ADDITION && $node->returnType !== null) {
133 40
            $this->getResult()->addRequirement(Reason::RETURN_TYPE, $node->getLine());
134
        }
135 28
    }
136
137 28
    private function detectAndHandleClassAliasCallToReservedName(Expr\FuncCall $call)
138 14
    {
139 8
        if ($call->name instanceof Node\Name && strcasecmp('class_alias', $call->name->getLast()) === 0
140 8
        ) {
141 8
            if (isset($call->args[1]) && $call->args[1]->value instanceof Node\Scalar\String_) {
142 4
                $value = $call->args[1]->value->value;
143 4
                $this->handleClassName($value, $call->args[1]->value->getLine());
144 28
            }
145
        }
146 54
    }
147
148 54
    private function detectAndHandleOperatorAdditions(Node $node)
149 50
    {
150 4
        if ($this->mode & self::MODE_ADDITION) {
151 2
            if ($node instanceof Expr\BinaryOp\Coalesce) {
152 50
                $this->getResult()->addRequirement(Reason::COALESCE_OPERATOR, $node->getLine());
153 4
            }
154 2
            if ($node instanceof Expr\BinaryOp\Spaceship) {
155 25
                $this->getResult()->addRequirement(Reason::SPACESHIP_OPERATOR, $node->getLine());
156 54
            }
157
        }
158 4
    }
159
160 4
    private function handleYieldFrom(Expr\YieldFrom $node)
161 2
    {
162 1
        if ($this->mode & self::MODE_ADDITION) {
163 4
            $this->getResult()->addRequirement(Reason::YIELD_FROM, $node->getLine());
164
        }
165 2
    }
166
167 2
    private function handleAnonymousClass(Node $node)
168 2
    {
169 1
        if ($this->mode & self::MODE_ADDITION) {
170 2
            $this->getResult()->addRequirement(Reason::ANON_CLASS, $node->getLine());
171
        }
172 4
    }
173
174 4
    private function handleNewAssignmentByRef(Expr\AssignRef $node)
175 2
    {
176 1
        if ($this->mode & self::MODE_REMOVAL) {
177 4
            $this->getResult()->addLimit(Reason::NEW_ASSIGN_BY_REF_REM, $node->getLine());
178
        }
179 38
    }
180
181 38
    private function handleClassName($name, $line = -1)
182 36
    {
183 36
        if ($name !== null) {
184 2
            $baseName = basename(str_replace('\\', '/', $name));
185 2
            if ($this->mode & self::MODE_DEPRECATION && $this->isNameSoftReserved($name)) {
186 36
                $this->getResult()->addLimit(Reason::SOFT_RESERVED_NAME, $line, null,
187 4
                    ['fqn' => $name, 'class' => $baseName]);
188 4
            } elseif ($this->mode & self::MODE_REMOVAL && $this->isNameReserved($name)) {
189 2
                $this->getResult()->addLimit(Reason::RESERVED_CLASS_NAME, $line, null,
190 18
                    ['fqn' => $name, 'class' => $baseName]);
191 38
            }
192
        }
193 8
    }
194
195 8
    private function isNameSoftReserved($name)
196 8
    {
197
        return in_array(strtolower(basename(str_replace('\\', '/', $name))),
198
            array_map('strtolower', self::$softReservedNames));
199 14
    }
200
201 14
    private function isNameReserved($name)
202 14
    {
203
        return in_array(strtolower(basename(str_replace('\\', '/', $name))),
204
            array_map('strtolower', self::$reservedNames));
205
    }
206
207
    private function handleDeclare(Node\Stmt\Declare_ $node)
208
    {
209
        if ($this->mode & self::MODE_ADDITION) {
210
            foreach ($node->declares as $declare) {
211
                if ($declare->key === 'strict_types') {
212
                    $this->getResult()->addRequirement(Reason::STRICT_TYPE_DECLARE, $declare->getLine());
213
                }
214
            }
215
        }
216
    }
217
}
218