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

ClassDefinition::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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