Completed
Pull Request — master (#112)
by Kévin
05:10
created

ClassDefinition::addInterface()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
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
13
/**
14
 * Class ClassDefinition
15
 * @package PHPSA\Definition
16
 */
17
class ClassDefinition extends ParentDefinition
18
{
19
    /**
20
     * @var int
21
     */
22
    protected $type;
23
24
    /**
25
     * Class methods
26
     *
27
     * @var ClassMethod[]
28
     */
29
    protected $methods = array();
30
31
    /**
32
     * Class properties
33
     *
34
     * @var Node\Stmt\Property[]
35
     */
36
    protected $properties = array();
37
38
    /**
39
     * Class constants
40
     *
41
     * @var Node\Stmt\Const_[]
42
     */
43
    protected $constants = array();
44
45
    /**
46
     * @todo Use Finder
47
     *
48
     * @var string
49
     */
50
    protected $filepath;
51
52
    /**
53
     * @var string|null
54
     */
55
    protected $extendsClass;
56
57
    /**
58
     * @var ClassDefinition|null
59
     */
60
    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...
61
62
    /**
63
     * @var array
64
     */
65
    protected $interfaces = array();
66
67
    /**
68
     * @param string $name
69
     * @param integer $type
70
     */
71 391
    public function __construct($name, $type)
72
    {
73 391
        $this->name = $name;
74 391
        $this->type = $type;
75 391
    }
76
77
    /**
78
     * @param ClassMethod $methodDefintion
79
     */
80 15
    public function addMethod(ClassMethod $methodDefintion)
81
    {
82 15
        $this->methods[$methodDefintion->getName()] = $methodDefintion;
83 15
    }
84
85
    /**
86
     * @param Node\Stmt\Property $property
87
     */
88 2
    public function addProperty(Node\Stmt\Property $property)
89
    {
90 2
        foreach ($property->props as $propertyDefinition) {
91 2
            $this->properties[$propertyDefinition->name] = $propertyDefinition;
92 2
        }
93 2
    }
94
95
    /**
96
     * @param Node\Stmt\ClassConst $const
97
     */
98
    public function addConst(Node\Stmt\ClassConst $const)
99
    {
100
        $this->constants[$const->consts[0]->name] = $const;
101
    }
102
103
    /**
104
     * @param Context $context
105
     * @return $this
106
     */
107 14
    public function compile(Context $context)
108
    {
109 14
        if ($this->compiled) {
110
            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...
111
        }
112
113 14
        $this->compiled = true;
114 14
        $context->setFilepath($this->filepath);
115 14
        $context->setScope($this);
116
117 14
        foreach ($this->methods as $method) {
118 14
            $context->clearSymbols();
119
120 14
            if (!$method->isStatic()) {
121 14
                $thisPtr = new Variable('this', $this, CompiledExpression::OBJECT);
122 14
                $thisPtr->incGets();
123 14
                $context->addVariable($thisPtr);
124 14
            }
125
126 14
            $method->compile($context);
127
128 14
            $symbols = $context->getSymbols();
129 14
            if (count($symbols) > 0) {
130 14
                foreach ($symbols as $name => $variable) {
131 14
                    if ($variable->isUnused()) {
132 1
                        $context->warning(
133 1
                            'unused-' . $variable->getSymbolType(),
134 1
                            sprintf(
135 1
                                'Unused ' . $variable->getSymbolType() . ' $%s in method %s()',
136 1
                                $variable->getName(),
137 1
                                $method->getName()
138 1
                            )
139 1
                        );
140 1
                    }
141 14
                }
142 14
            }
143 14
        }
144
145
        // check for old-style constructors
146 14
        if ($this->hasMethod($this->getName())) {
147
            $context->warning(
148
                'old-style-constructor',
149
                sprintf(
150
                    'Class %s has a deprecated constructor: methods with the same name as their class will not be '.
151
                    'constructors in a future version of PHP',
152
                    $this->getName()
153
                )
154
            );
155
        }
156
157 14
        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...
158
    }
159
160
    /**
161
     * @param string $name
162
     * @param boolean|false $inherit
163
     * @return bool
164
     */
165 15
    public function hasMethod($name, $inherit = false)
166
    {
167 15
        if (isset($this->methods[$name])) {
168 1
            return true;
169
        }
170
171 15
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasMethod($name, $inherit)) {
172
            $method = $this->extendsClassDefinition->getMethod($name, $inherit);
173
            return $method && ($method->isPublic() || $method->isProtected());
174
        }
175
176 15
        return false;
177
    }
178
179
    /**
180
     * @param $name
181
     * @return bool
182
     */
183
    public function hasConst($name)
184
    {
185
        return isset($this->constants[$name]);
186
    }
187
188
    /**
189
     * @param $name
190
     * @param boolean|false $inherit
191
     * @return ClassMethod|null
192
     */
193 1
    public function getMethod($name, $inherit = false)
194
    {
195 1
        if (isset($this->methods[$name])) {
196 1
            return $this->methods[$name];
197
        }
198
199
        if ($inherit && $this->extendsClassDefinition) {
200
            return $this->extendsClassDefinition->getMethod($name, $inherit);
201
        }
202
203
        return null;
204
    }
205
206
    /**
207
     * @param $name
208
     * @param bool $inherit
209
     * @return bool
210
     */
211 1
    public function hasProperty($name, $inherit = false)
212
    {
213 1
        if (isset($this->properties[$name])) {
214 1
            return isset($this->properties[$name]);
215
        }
216
217 1
        return $inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasProperty($name, true);
218
    }
219
220
    /**
221
     * @param string $name
222
     * @param bool $inherit
223
     * @return Node\Stmt\Property
224
     */
225
    public function getProperty($name, $inherit = false)
226
    {
227
        assert($this->hasProperty($name, $inherit));
228
229
        if (isset($this->properties[$name])) {
230
            return $this->properties[$name];
231
        }
232
233
        if ($inherit && $this->extendsClassDefinition) {
234
            return $this->extendsClassDefinition->getProperty($name, true);
235
        }
236
237
        return null;
238
    }
239
240
    /**
241
     * @return string
242
     */
243 14
    public function getFilepath()
244
    {
245 14
        return $this->filepath;
246
    }
247
248
    /**
249
     * @param string $filepath
250
     */
251 14
    public function setFilepath($filepath)
252
    {
253 14
        $this->filepath = $filepath;
254 14
    }
255
256
    /**
257
     * @return bool
258
     */
259
    public function isAbstract()
260
    {
261
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_ABSTRACT);
262
    }
263
264
    /**
265
     * @param null|string $extendsClass
266
     */
267
    public function setExtendsClass($extendsClass)
268
    {
269
        $this->extendsClass = $extendsClass;
270
    }
271
272
    /**
273
     * @return null|ClassDefinition
274
     */
275
    public function getExtendsClassDefinition()
276
    {
277
        return $this->extendsClassDefinition;
278
    }
279
280
    /**
281
     * @param ClassDefinition $extendsClassDefinition
282
     */
283
    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...
284
    {
285
        $this->extendsClassDefinition = $extendsClassDefinition;
286
    }
287
288
    /**
289
     * @param array $interface
290
     */
291
    public function addInterface($interface)
292
    {
293
        $this->interfaces[] = $interface;
294
    }
295
296
    /**
297
     * @return null|string
298
     */
299 14
    public function getExtendsClass()
300
    {
301 14
        return $this->extendsClass;
302
    }
303
}
304