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