Completed
Pull Request — master (#132)
by Enrico
02:58
created

ClassDefinition::compile()   B

Complexity

Conditions 8
Paths 19

Size

Total Lines 58
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 8.0007

Importance

Changes 0
Metric Value
cc 8
eloc 35
c 0
b 0
f 0
nc 19
nop 1
dl 0
loc 58
ccs 44
cts 45
cp 0.9778
crap 8.0007
rs 7.1977

How to fix   Long Method   

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 448
    public function __construct($name, Node\Stmt\Class_ $statement = null, $type = 0)
79
    {
80 448
        $this->name = $name;
81 448
        $this->statement = $statement;
82 448
        $this->type = $type;
83 448
    }
84
85
    /**
86
     * @param ClassMethod $methodDefintion
87
     */
88 20
    public function addMethod(ClassMethod $methodDefintion)
89
    {
90 20
        $this->methods[$methodDefintion->getName()] = $methodDefintion;
91 20
    }
92
93
    /**
94
     * @param Node\Stmt\Property $property
95
     */
96 2
    public function addProperty(Node\Stmt\Property $property)
97
    {
98 2
        foreach ($property->props as $propertyDefinition) {
99 2
            $this->properties[$propertyDefinition->name] = $propertyDefinition;
100 2
        }
101 2
    }
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 20
    public function compile(Context $context)
116
    {
117 20
        if ($this->compiled) {
118
            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 20
        $context->getEventManager()->fire(
122 20
            Event\StatementBeforeCompile::EVENT_NAME,
123 20
            new Event\StatementBeforeCompile(
124 20
                $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 20
            )
127 20
        );
128
129 20
        $this->compiled = true;
130 20
        $context->setFilepath($this->filepath);
131 20
        $context->setScope($this);
132
133 20
        foreach ($this->constants as $const) {
134 1
            $context->getEventManager()->fire(
135 1
                Event\StatementBeforeCompile::EVENT_NAME,
136 1
                new Event\StatementBeforeCompile(
137 1
                    $const,
138
                    $context
139 1
                )
140 1
            );
141 20
        }
142
143 20
        foreach ($this->methods as $method) {
144 19
            $context->clearSymbols();
145
146 19
            if (!$method->isStatic()) {
147 19
                $thisPtr = new Variable('this', $this, CompiledExpression::OBJECT);
148 19
                $thisPtr->incGets();
149 19
                $context->addVariable($thisPtr);
150 19
            }
151
152 19
            $method->compile($context);
153
154 19
            $symbols = $context->getSymbols();
155 19
            if (count($symbols) > 0) {
156 19
                foreach ($symbols as $name => $variable) {
157 19
                    if ($variable->isUnused()) {
158 2
                        $context->warning(
159 2
                            'unused-' . $variable->getSymbolType(),
160 2
                            sprintf(
161 2
                                'Unused ' . $variable->getSymbolType() . ' $%s in method %s()',
162 2
                                $variable->getName(),
163 2
                                $method->getName()
164 2
                            )
165 2
                        );
166 2
                    }
167 19
                }
168 19
            }
169 20
        }
170
171 20
        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...
172
    }
173
174
    /**
175
     * @param string $name
176
     * @param boolean|false $inherit
177
     * @return bool
178
     */
179 1
    public function hasMethod($name, $inherit = false)
180
    {
181 1
        if (isset($this->methods[$name])) {
182 1
            return true;
183
        }
184
185 1
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasMethod($name, $inherit)) {
186
            $method = $this->extendsClassDefinition->getMethod($name, $inherit);
187
            return $method && ($method->isPublic() || $method->isProtected());
188
        }
189
190 1
        return false;
191
    }
192
193
    /**
194
     * @param string $name
195
     * @param bool $inherit
196
     * @return bool
197
     */
198 2
    public function hasConst($name, $inherit = false)
199
    {
200 2
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasConst($name, $inherit)) {
201 1
            return true;
202
        }
203
204 2
        return isset($this->constants[$name]);
205
    }
206
207
    /**
208
     * @param $name
209
     * @param boolean|false $inherit
210
     * @return ClassMethod|null
211
     */
212 1
    public function getMethod($name, $inherit = false)
213
    {
214 1
        if (isset($this->methods[$name])) {
215 1
            return $this->methods[$name];
216
        }
217
218
        if ($inherit && $this->extendsClassDefinition) {
219
            return $this->extendsClassDefinition->getMethod($name, $inherit);
220
        }
221
222
        return null;
223
    }
224
225
    /**
226
     * @param $name
227
     * @param bool $inherit
228
     * @return bool
229
     */
230 1
    public function hasProperty($name, $inherit = false)
231
    {
232 1
        if (isset($this->properties[$name])) {
233 1
            return isset($this->properties[$name]);
234
        }
235
236 1
        return $inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasProperty($name, true);
237
    }
238
239
    /**
240
     * @param string $name
241
     * @param bool $inherit
242
     * @return Node\Stmt\Property
243
     */
244
    public function getProperty($name, $inherit = false)
245
    {
246
        assert($this->hasProperty($name, $inherit));
247
248
        if (isset($this->properties[$name])) {
249
            return $this->properties[$name];
250
        }
251
252
        if ($inherit && $this->extendsClassDefinition) {
253
            return $this->extendsClassDefinition->getProperty($name, true);
254
        }
255
256
        return null;
257
    }
258
259
    /**
260
     * @return string
261
     */
262 20
    public function getFilepath()
263
    {
264 20
        return $this->filepath;
265
    }
266
267
    /**
268
     * @param string $filepath
269
     */
270 20
    public function setFilepath($filepath)
271
    {
272 20
        $this->filepath = $filepath;
273 20
    }
274
275
    /**
276
     * @return bool
277
     */
278
    public function isAbstract()
279
    {
280
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_ABSTRACT);
281
    }
282
283
    /**
284
     * @param null|string $extendsClass
285
     */
286
    public function setExtendsClass($extendsClass)
287
    {
288
        $this->extendsClass = $extendsClass;
289
    }
290
291
    /**
292
     * @return null|ClassDefinition
293
     */
294
    public function getExtendsClassDefinition()
295
    {
296
        return $this->extendsClassDefinition;
297
    }
298
299
    /**
300
     * @param ClassDefinition $extendsClassDefinition
301
     */
302 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...
303
    {
304 1
        $this->extendsClassDefinition = $extendsClassDefinition;
305 1
    }
306
307
    /**
308
     * @param array $interface
309
     */
310
    public function addInterface($interface)
311
    {
312
        $this->interfaces[] = $interface;
313
    }
314
315
    /**
316
     * @return null|string
317
     */
318 20
    public function getExtendsClass()
319
    {
320 20
        return $this->extendsClass;
321
    }
322
}
323