Completed
Push — master ( a323dc...8dfe2a )
by Enrico
04:19
created

ClassDefinition::isFinal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Definition;
7
8
use PHPSA\CompiledExpression;
9
use PHPSA\Context;
10
use PhpParser\Node;
11
use PHPSA\Variable;
12
use PHPSA\Compiler\Event;
13
14
/**
15
 * Class ClassDefinition
16
 * @package PHPSA\Definition
17
 */
18
class ClassDefinition extends ParentDefinition
0 ignored issues
show
Complexity introduced by
This class has a complexity of 59 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
19
{
20
    /**
21
     * @var int
22
     */
23
    protected $type;
24
25
    /**
26
     * Class methods
27
     *
28
     * @var ClassMethod[]
29
     */
30
    protected $methods = array();
31
32
    /**
33
     * Class properties
34
     *
35
     * @var Node\Stmt\PropertyProperty[]
36
     */
37
    protected $properties = array();
38
39
    /**
40
     * Property Statements
41
     *
42
     * @var Node\Stmt\Property[]
43
     */
44
    protected $propertyStatements = array();
45
46
    /**
47
     * Class constants
48
     *
49
     * @var Node\Stmt\Const_[]
50
     */
51
    protected $constants = array();
52
53
    /**
54
     * @todo Use Finder
55
     *
56
     * @var string
57
     */
58
    protected $filepath;
59
60
    /**
61
     * @var Node\Stmt\Class_|null
62
     */
63
    protected $statement;
64
65
    /**
66
     * @var string|null
67
     */
68
    protected $extendsClass;
69
70
    /**
71
     * @var ClassDefinition|null
72
     */
73
    protected $extendsClassDefinition;
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $extendsClassDefinition exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
74
75
    /**
76
     * @var array
77
     */
78
    protected $interfaces = array();
79
80
    /**
81
     * @param string $name
82
     * @param Node\Stmt\Class_ $statement
83
     * @param integer $type
84
     */
85 861
    public function __construct($name, Node\Stmt\Class_ $statement = null, $type = 0)
86
    {
87 861
        $this->name = $name;
88 861
        $this->statement = $statement;
89 861
        $this->type = $type;
90 861
    }
91
92
    /**
93
     * @param ClassMethod $classMethod
94
     * @param bool $overwrite Should we overwrite method if it already exists
95
     * @return bool Did we overwrite method?
96
     */
97 27
    public function addMethod(ClassMethod $classMethod, $overwrite = true)
98
    {
99 27
        if ($overwrite) {
100 27
            $this->methods[$classMethod->getName()] = $classMethod;
101 27
        } else {
102
            $name = $classMethod->getName();
103
            if (isset($this->methods[$name])) {
104
                return false;
105
            } else {
106
                $this->methods[$name] = $classMethod;
107
            }
108
        }
109
110 27
        return true;
111
    }
112
113
    /**
114
     * @param Node\Stmt\Property $property
115
     */
116 6
    public function addProperty(Node\Stmt\Property $property)
117
    {
118 6
        foreach ($property->props as $propertyDefinition) {
119 5
            $this->properties[$propertyDefinition->name] = $propertyDefinition;
120 5
        }
121
122 5
        $this->propertyStatements[] = $property;
123 5
    }
124
125
    /**
126
     * @param Node\Stmt\ClassConst $const
127
     */
128 3
    public function addConst(Node\Stmt\ClassConst $const)
129
    {
130 3
        $this->constants[$const->consts[0]->name] = $const;
131 3
    }
132
133
    /**
134
     * @param Context $context
135
     * @return $this
136
     */
137 28
    public function compile(Context $context)
0 ignored issues
show
Complexity introduced by
This operation has 216 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
138
    {
139 28
        if ($this->compiled) {
140
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type documented by PHPSA\Definition\ClassDefinition::compile of type PHPSA\Definition\ClassDefinition.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
        }
142
143 28
        $context->getEventManager()->fire(
144 28
            Event\StatementBeforeCompile::EVENT_NAME,
145 28
            new Event\StatementBeforeCompile(
146 28
                $this->statement,
0 ignored issues
show
Bug introduced by
It seems like $this->statement can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
147
                $context
148 28
            )
149 28
        );
150
151 28
        $this->compiled = true;
152 28
        $context->setFilepath($this->filepath);
153 28
        $context->setScope($this);
154
155
        // Compile event for properties
156 28
        foreach ($this->properties as $property) {
157 4
            if (!$property->default) {
158 2
                continue;
159
            }
160
161
            // fire expression event for property default
162 3
            $context->getEventManager()->fire(
163 3
                Event\ExpressionBeforeCompile::EVENT_NAME,
164 3
                new Event\ExpressionBeforeCompile(
165 3
                    $property->default,
166
                    $context
167 3
                )
168 3
            );
169 28
        }
170
171
        // Compile event for constants
172 28
        foreach ($this->constants as $const) {
173 1
            $context->getEventManager()->fire(
174 1
                Event\StatementBeforeCompile::EVENT_NAME,
175 1
                new Event\StatementBeforeCompile(
176 1
                    $const,
177
                    $context
178 1
                )
179 1
            );
180 28
        }
181
182
        // Compiler event for property statements
183 28
        foreach ($this->propertyStatements as $prop) {
184 4
            $context->getEventManager()->fire(
185 4
                Event\StatementBeforeCompile::EVENT_NAME,
186 4
                new Event\StatementBeforeCompile(
187 4
                    $prop,
188
                    $context
189 4
                )
190 4
            );
191 28
        }
192
193
        // Compile each method
194 28
        foreach ($this->methods as $method) {
195 26
            $context->clearSymbols();
196
197 26
            if (!$method->isStatic()) {
198 25
                $thisPtr = new Variable('this', $this, CompiledExpression::OBJECT);
199 25
                $thisPtr->incGets();
200 25
                $context->addVariable($thisPtr);
201 25
            }
202
203 26
            $method->compile($context);
204
205 26
            $symbols = $context->getSymbols();
206 26
            if (count($symbols) > 0) {
207 25
                foreach ($symbols as $name => $variable) {
208 25
                    if ($variable->isUnused()) {
209 4
                        $context->warning(
210 4
                            'unused-' . $variable->getSymbolType(),
211 4
                            sprintf(
212 4
                                'Unused ' . $variable->getSymbolType() . ' $%s in method %s()',
213 4
                                $variable->getName(),
214 4
                                $method->getName()
215 4
                            )
216 4
                        );
217 4
                    }
218 25
                }
219 25
            }
220 28
        }
221
222 28
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (PHPSA\Definition\ClassDefinition) is incompatible with the return type declared by the abstract method PHPSA\Definition\AbstractDefinition::compile of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
223
    }
224
225
    /**
226
     * @param string $name
227
     * @param boolean|false $inherit
228
     * @return bool
229
     */
230 2
    public function hasMethod($name, $inherit = false)
231
    {
232 2
        if (isset($this->methods[$name])) {
233 2
            return true;
234
        }
235
236 1
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasMethod($name, $inherit)) {
237
            $method = $this->extendsClassDefinition->getMethod($name, $inherit);
238
            return $method && ($method->isPublic() || $method->isProtected());
239
        }
240
241 1
        return false;
242
    }
243
244
    /**
245
     * @param string $name
246
     * @param bool $inherit
247
     * @return bool
248
     */
249 2
    public function hasConst($name, $inherit = false)
250
    {
251 2
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasConst($name, $inherit)) {
252 1
            return true;
253
        }
254
255 2
        return isset($this->constants[$name]);
256
    }
257
258
    /**
259
     * @param $name
260
     * @param boolean|false $inherit
261
     * @return ClassMethod|null
262
     */
263 2
    public function getMethod($name, $inherit = false)
264
    {
265 2
        if (isset($this->methods[$name])) {
266 2
            return $this->methods[$name];
267
        }
268
269
        if ($inherit && $this->extendsClassDefinition) {
270
            return $this->extendsClassDefinition->getMethod($name, $inherit);
271
        }
272
273
        return null;
274
    }
275
276
    /**
277
     * @param $name
278
     * @param bool $inherit
279
     * @return bool
280
     */
281 3
    public function hasProperty($name, $inherit = false)
282
    {
283 3
        if (isset($this->properties[$name])) {
284 1
            return isset($this->properties[$name]);
285
        }
286
287 3
        return $inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasProperty($name, true);
288
    }
289
290
    /**
291
     * @param string $name
292
     * @param bool $inherit
293
     * @return Node\Stmt\Property
294
     */
295
    public function getProperty($name, $inherit = false)
296
    {
297
        assert($this->hasProperty($name, $inherit));
298
299
        if (isset($this->properties[$name])) {
300
            return $this->properties[$name];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->properties[$name]; (PhpParser\Node\Stmt\PropertyProperty) is incompatible with the return type documented by PHPSA\Definition\ClassDefinition::getProperty of type PhpParser\Node\Stmt\Property.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
301
        }
302
303
        if ($inherit && $this->extendsClassDefinition) {
304
            return $this->extendsClassDefinition->getProperty($name, true);
305
        }
306
307
        return null;
308
    }
309
310
    /**
311
     * @return string
312
     */
313 28
    public function getFilepath()
314
    {
315 28
        return $this->filepath;
316
    }
317
318
    /**
319
     * @param string $filepath
320
     */
321 28
    public function setFilepath($filepath)
322
    {
323 28
        $this->filepath = $filepath;
324 28
    }
325
326
    /**
327
     * @return bool
328
     */
329
    public function isAbstract()
330
    {
331
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_ABSTRACT);
332
    }
333
334
    /**
335
     * @return bool
336
     */
337 1
    public function isFinal()
338
    {
339 1
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_FINAL);
340
    }
341
342
    /**
343
     * @param null|string $extendsClass
344
     */
345
    public function setExtendsClass($extendsClass)
346
    {
347
        $this->extendsClass = $extendsClass;
348
    }
349
350
    /**
351
     * @return null|ClassDefinition
352
     */
353
    public function getExtendsClassDefinition()
354
    {
355
        return $this->extendsClassDefinition;
356
    }
357
358
    /**
359
     * @param ClassDefinition $extendsClassDefinition
360
     */
361 1
    public function setExtendsClassDefinition(ClassDefinition $extendsClassDefinition)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $extendsClassDefinition exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
362
    {
363 1
        $this->extendsClassDefinition = $extendsClassDefinition;
364 1
    }
365
366
    /**
367
     * @param string $interface
368
     */
369
    public function addInterface($interface)
370
    {
371
        $this->interfaces[] = $interface;
372
    }
373
374
    /**
375
     * @return null|string
376
     */
377 28
    public function getExtendsClass()
378
    {
379 28
        return $this->extendsClass;
380
    }
381
382
    /**
383
     * @param TraitDefinition $definition
384
     * @param Node\Stmt\TraitUseAdaptation\Alias[] $adaptations
385
     */
386
    public function mergeTrait(TraitDefinition $definition, array $adaptations)
387
    {
388
        $methods = $definition->getMethods();
389
        if ($methods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $methods of type PHPSA\Definition\ClassMethod[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
390
            foreach ($adaptations as $adaptation) {
391
                // We don't support Trait name for now
392
                if (!$adaptation->trait) {
393
                    $methodNameFromTrait = $adaptation->method;
394
                    if (isset($methods[$methodNameFromTrait])) {
395
                        /** @var ClassMethod $method Method from Trait */
396
                        $method = $methods[$methodNameFromTrait];
397
                        if ($adaptation->newName
0 ignored issues
show
Bug Best Practice introduced by
The expression $adaptation->newName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
398
                            || ($adaptation->newModifier && $method->getModifier() != $adaptation->newModifier)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $adaptation->newModifier of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
399
                            // Don't modify original method from Trait
400
                            $method = clone $method;
401
                            $method->setName($adaptation->newName);
402
                            $method->setModifier($adaptation->newModifier);
403
404
                            $methods[$methodNameFromTrait] = $method;
405
                        }
406
                    }
407
                }
408
            }
409
410
            foreach ($methods as $method) {
411
                $this->addMethod($method, false);
412
            }
413
        }
414
    }
415
}
416