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

ClassDefinition::compile()   C

Complexity

Conditions 10
Paths 55

Size

Total Lines 76
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 55
CRAP Score 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 43
nc 55
nop 1
dl 0
loc 76
ccs 55
cts 55
cp 1
crap 10
rs 5.7198
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Property[]
36
     */
37
    protected $properties = array();
38
39
    /**
40
     * Class constants
41
     *
42
     * @var Node\Stmt\Const_[]
43
     */
44
    protected $constants = array();
45
46
    /**
47
     * @todo Use Finder
48
     *
49
     * @var string
50
     */
51
    protected $filepath;
52
53
    /**
54
     * @var Node\Stmt\Class_|null
55
     */
56
    protected $statement;
57
58
    /**
59
     * @var string|null
60
     */
61
    protected $extendsClass;
62
63
    /**
64
     * @var ClassDefinition|null
65
     */
66
    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...
67
68
    /**
69
     * @var array
70
     */
71
    protected $interfaces = array();
72
73
    /**
74
     * @param string $name
75
     * @param Node\Stmt\Class_ $statement
76
     * @param integer $type
77
     */
78 820
    public function __construct($name, Node\Stmt\Class_ $statement = null, $type = 0)
79
    {
80 820
        $this->name = $name;
81 820
        $this->statement = $statement;
82 820
        $this->type = $type;
83 820
    }
84
85
    /**
86
     * @param ClassMethod $methodDefintion
87
     */
88 24
    public function addMethod(ClassMethod $methodDefintion)
89
    {
90 24
        $this->methods[$methodDefintion->getName()] = $methodDefintion;
91 24
    }
92
93
    /**
94
     * @param Node\Stmt\Property $property
95
     */
96 3
    public function addProperty(Node\Stmt\Property $property)
97
    {
98 3
        foreach ($property->props as $propertyDefinition) {
99 3
            $this->properties[$propertyDefinition->name] = $propertyDefinition;
100 3
        }
101 3
    }
102
103
    /**
104
     * @param Node\Stmt\ClassConst $const
105
     */
106 3
    public function addConst(Node\Stmt\ClassConst $const)
107
    {
108 3
        $this->constants[$const->consts[0]->name] = $const;
109 3
    }
110
111
    /**
112
     * @param Context $context
113
     * @return $this
114
     */
115 24
    public function compile(Context $context)
116
    {
117 24
        if ($this->compiled) {
118 1
            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...
119
        }
120
121 24
        $context->getEventManager()->fire(
122 24
            Event\StatementBeforeCompile::EVENT_NAME,
123 24
            new Event\StatementBeforeCompile(
124 24
                $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...
125
                $context
126 24
            )
127 24
        );
128
129 24
        $this->compiled = true;
130 24
        $context->setFilepath($this->filepath);
131 24
        $context->setScope($this);
132
133
        // Compile event for properties
134 24
        foreach ($this->properties as $property) {
135 2
            if (!$property->default) {
0 ignored issues
show
Bug introduced by
The property default does not seem to exist in PhpParser\Node\Stmt\Property.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

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