Completed
Pull Request — master (#134)
by Enrico
04:05
created

ClassDefinition::addProperty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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