Completed
Pull Request — master (#157)
by Kévin
03:46
created

ClassDefinition::addInterfaceDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
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 54 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 string[]
77
     */
78
    protected $interfaces = [];
79
80
    /**
81
     * @var InterfaceDefinition[]
82
     */
83
    protected $interfacesDefinitions = [];
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $interfacesDefinitions 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...
84
85
    /**
86
     * @param string $name
87
     * @param Node\Stmt\Class_ $statement
88
     * @param integer $type
89
     */
90 858
    public function __construct($name, Node\Stmt\Class_ $statement = null, $type = 0)
91
    {
92 858
        $this->name = $name;
93 858
        $this->statement = $statement;
94 858
        $this->type = $type;
95 858
    }
96
97
    /**
98
     * @param ClassMethod $methodDefintion
99
     */
100 25
    public function addMethod(ClassMethod $methodDefintion)
101
    {
102 25
        $this->methods[$methodDefintion->getName()] = $methodDefintion;
103 25
    }
104
105
    /**
106
     * @param Node\Stmt\Property $property
107
     */
108 4
    public function addProperty(Node\Stmt\Property $property)
109
    {
110 4
        foreach ($property->props as $propertyDefinition) {
111 4
            $this->properties[$propertyDefinition->name] = $propertyDefinition;
112 4
        }
113
114 4
        $this->propertyStatements[] = $property;
115 4
    }
116
117
    /**
118
     * @param Node\Stmt\ClassConst $const
119
     */
120 3
    public function addConst(Node\Stmt\ClassConst $const)
121
    {
122 3
        $this->constants[$const->consts[0]->name] = $const;
123 3
    }
124
125
    /**
126
     * @param Context $context
127
     * @return $this
128
     */
129 25
    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...
130
    {
131 25
        if ($this->compiled) {
132
            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...
133
        }
134
135 25
        $context->getEventManager()->fire(
136 25
            Event\StatementBeforeCompile::EVENT_NAME,
137 25
            new Event\StatementBeforeCompile(
138 25
                $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...
139
                $context
140 25
            )
141 25
        );
142
143 25
        $this->compiled = true;
144 25
        $context->setFilepath($this->filepath);
145 25
        $context->setScope($this);
146
147
        // Compile event for properties
148 25
        foreach ($this->properties as $property) {
149 3
            if (!$property->default) {
150 1
                continue;
151
            }
152
153
            // fire expression event for property default
154 2
            $context->getEventManager()->fire(
155 2
                Event\ExpressionBeforeCompile::EVENT_NAME,
156 2
                new Event\ExpressionBeforeCompile(
157 2
                    $property->default,
158
                    $context
159 2
                )
160 2
            );
161 25
        }
162
163
        // Compile event for constants
164 25
        foreach ($this->constants as $const) {
165 1
            $context->getEventManager()->fire(
166 1
                Event\StatementBeforeCompile::EVENT_NAME,
167 1
                new Event\StatementBeforeCompile(
168 1
                    $const,
169
                    $context
170 1
                )
171 1
            );
172 25
        }
173
174
        // Compiler event for property statements
175 25
        foreach ($this->propertyStatements as $prop) {
176 3
            $context->getEventManager()->fire(
177 3
                Event\StatementBeforeCompile::EVENT_NAME,
178 3
                new Event\StatementBeforeCompile(
179 3
                    $prop,
180
                    $context
181 3
                )
182 3
            );
183 25
        }
184
185
        // Compile each method
186 25
        foreach ($this->methods as $method) {
187 24
            $context->clearSymbols();
188
189 24
            if (!$method->isStatic()) {
190 24
                $thisPtr = new Variable('this', $this, CompiledExpression::OBJECT);
191 24
                $thisPtr->incGets();
192 24
                $context->addVariable($thisPtr);
193 24
            }
194
195 24
            $method->compile($context);
196
197 24
            $symbols = $context->getSymbols();
198 24
            if (count($symbols) > 0) {
199 24
                foreach ($symbols as $name => $variable) {
200 24
                    if ($variable->isUnused()) {
201 3
                        $context->warning(
202 3
                            'unused-' . $variable->getSymbolType(),
203 3
                            sprintf(
204 3
                                'Unused ' . $variable->getSymbolType() . ' $%s in method %s()',
205 3
                                $variable->getName(),
206 3
                                $method->getName()
207 3
                            )
208 3
                        );
209 3
                    }
210 24
                }
211 24
            }
212 25
        }
213
214 25
        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...
215
    }
216
217
    /**
218
     * @param string $name
219
     * @param boolean|false $inherit
220
     * @return bool
221
     */
222 1
    public function hasMethod($name, $inherit = false)
223
    {
224 1
        if (isset($this->methods[$name])) {
225 1
            return true;
226
        }
227
228 1
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasMethod($name, $inherit)) {
229
            $method = $this->extendsClassDefinition->getMethod($name, $inherit);
230
            return $method && ($method->isPublic() || $method->isProtected());
231
        }
232
233 1
        return false;
234
    }
235
236
    /**
237
     * @param string $name
238
     * @param bool $inherit
239
     * @return bool
240
     */
241 2
    public function hasConst($name, $inherit = false)
242
    {
243 2
        if ($inherit) {
244 1
            if ($this->extendsClassDefinition && $this->extendsClassDefinition->hasConst($name, $inherit)) {
245 1
                return true;
246
            }
247
248
            /** @var InterfaceDefinition $interface */
249 1
            foreach ($this->interfacesDefinitions as $interface) {
250
                if ($interface->hasConst($name, true)) {
251
                    return true;
252
                }
253 1
            }
254 1
        }
255
256 2
        return isset($this->constants[$name]);
257
    }
258
259
    /**
260
     * @param $name
261
     * @param boolean|false $inherit
262
     * @return ClassMethod|null
263
     */
264 1
    public function getMethod($name, $inherit = false)
265
    {
266 1
        if (isset($this->methods[$name])) {
267 1
            return $this->methods[$name];
268
        }
269
270
        if ($inherit && $this->extendsClassDefinition) {
271
            return $this->extendsClassDefinition->getMethod($name, $inherit);
272
        }
273
274
        return null;
275
    }
276
277
    /**
278
     * @param $name
279
     * @param bool $inherit
280
     * @return bool
281
     */
282 3
    public function hasProperty($name, $inherit = false)
283
    {
284 3
        if (isset($this->properties[$name])) {
285 1
            return isset($this->properties[$name]);
286
        }
287
288 3
        return $inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasProperty($name, true);
289
    }
290
291
    /**
292
     * @param string $name
293
     * @param bool $inherit
294
     * @return Node\Stmt\Property
295
     */
296
    public function getProperty($name, $inherit = false)
297
    {
298
        assert($this->hasProperty($name, $inherit));
299
300
        if (isset($this->properties[$name])) {
301
            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...
302
        }
303
304
        if ($inherit && $this->extendsClassDefinition) {
305
            return $this->extendsClassDefinition->getProperty($name, true);
306
        }
307
308
        return null;
309
    }
310
311
    /**
312
     * @return string
313
     */
314 25
    public function getFilepath()
315
    {
316 25
        return $this->filepath;
317
    }
318
319
    /**
320
     * @param string $filepath
321
     */
322 25
    public function setFilepath($filepath)
323
    {
324 25
        $this->filepath = $filepath;
325 25
    }
326
327
    /**
328
     * @return bool
329
     */
330
    public function isAbstract()
331
    {
332
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_ABSTRACT);
333
    }
334
335
    /**
336
     * @param null|string $extendsClass
337
     */
338
    public function setExtendsClass($extendsClass)
339
    {
340
        $this->extendsClass = $extendsClass;
341
    }
342
343
    /**
344
     * @return null|ClassDefinition
345
     */
346
    public function getExtendsClassDefinition()
347
    {
348
        return $this->extendsClassDefinition;
349
    }
350
351
    /**
352
     * @param ClassDefinition $extendsClassDefinition
353
     */
354 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...
355
    {
356 1
        $this->extendsClassDefinition = $extendsClassDefinition;
357 1
    }
358
359
    /**
360
     * @param string $interface
361
     */
362
    public function addInterface($interface)
363
    {
364
        $this->interfaces[] = $interface;
365
    }
366
367
    /**
368
     * @return string[]
369
     */
370 25
    public function getInterfaces()
371
    {
372 25
        return $this->interfaces;
373
    }
374
375
    /**
376
     * @param InterfaceDefinition $interfaceDefinition
377
     */
378
    public function addInterfaceDefinition(InterfaceDefinition $interfaceDefinition)
379
    {
380
        $this->interfacesDefinitions[] = $interfaceDefinition;
381
    }
382
383
    /**
384
     * @return null|string
385
     */
386 25
    public function getExtendsClass()
387
    {
388 25
        return $this->extendsClass;
389
    }
390
391
    /**
392
     * @param TraitDefinition $definition
393
     */
394
    public function mergeTrait(TraitDefinition $definition)
395
    {
396
        $methods = $definition->getMethods();
397
        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...
398
            foreach ($methods as $method) {
399
                $this->addMethod($method);
400
            }
401
        }
402
    }
403
}
404