Completed
Push — master ( 3437ad...7f55a3 )
by Дмитрий
06:31 queued 02:33
created

ClassDefinition   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 8

Test Coverage

Coverage 25.32%

Importance

Changes 12
Bugs 1 Features 1
Metric Value
wmc 31
c 12
b 1
f 1
lcom 5
cbo 8
dl 0
loc 249
ccs 20
cts 79
cp 0.2532
rs 9.8

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A addMethod() 0 4 1
A addProperty() 0 4 1
A addConst() 0 4 1
B compile() 0 35 6
B hasMethod() 0 13 6
A hasConst() 0 4 1
A getMethod() 0 8 4
A hasProperty() 0 4 1
A getProperty() 0 5 1
A getFilepath() 0 4 1
A setFilepath() 0 4 1
A isAbstract() 0 4 1
A setExtendsClass() 0 4 1
A getExtendsClassDefinition() 0 4 1
A setExtendsClassDefinition() 0 4 1
A addInterface() 0 4 1
A getExtendsClass() 0 4 1
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\Compiler\Parameter;
10
use PHPSA\Context;
11
use PhpParser\Node;
12
use PHPSA\Variable;
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 string|null
55
     */
56
    protected $extendsClass;
57
58
    /**
59
     * @var ClassDefinition|null
60
     */
61
    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...
62
63
    /**
64
     * @var array
65
     */
66
    protected $interfaces = array();
67
68
    /**
69
     * @param string $name
70
     * @param integer $type
71
     */
72 370
    public function __construct($name, $type)
73
    {
74 370
        $this->name = $name;
75 370
        $this->type = $type;
76 370
    }
77
78
    /**
79
     * @param ClassMethod $methodDefintion
80
     */
81 1
    public function addMethod(ClassMethod $methodDefintion)
82
    {
83 1
        $this->methods[$methodDefintion->getName()] = $methodDefintion;
84 1
    }
85
86
    /**
87
     * @param Node\Stmt\Property $property
88
     */
89 1
    public function addProperty(Node\Stmt\Property $property)
90
    {
91 1
        $this->properties[$property->props[0]->name] = $property;
92 1
    }
93
94
    /**
95
     * @param Node\Stmt\ClassConst $const
96
     */
97
    public function addConst(Node\Stmt\ClassConst $const)
98
    {
99
        $this->constants[$const->consts[0]->name] = $const;
100
    }
101
102
    /**
103
     * @param Context $context
104
     * @return $this
105
     */
106
    public function compile(Context $context)
107
    {
108
        $context->setFilepath($this->filepath);
109
        $context->setScope($this);
110
111
        foreach ($this->methods as $method) {
112
            $context->clearSymbols();
113
114
            if (!$method->isStatic()) {
115
                $thisPtr = new Variable('this', $this, CompiledExpression::OBJECT);
116
                $thisPtr->incGets();
117
                $context->addVariable($thisPtr);
118
            }
119
120
            $method->compile($context);
121
122
            $symbols = $context->getSymbols();
123
            if (count($symbols) > 0) {
124
                foreach ($symbols as $name => $variable) {
125
                    if ($variable->isUnused()) {
126
                        $context->warning(
127
                            'unused-' . $variable->getSymbolType(),
128
                            sprintf(
129
                                'Unused ' . $variable->getSymbolType() . ' $%s in method %s()',
130
                                $variable->getName(),
131
                                $method->getName()
132
                            )
133
                        );
134
                    }
135
                }
136
            }
137
        }
138
139
        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...
140
    }
141
142
    /**
143
     * @param string $name
144
     * @param boolean|false $inherit
145
     * @return bool
146
     */
147 1
    public function hasMethod($name, $inherit = false)
148
    {
149 1
        if (isset($this->methods[$name])) {
150 1
            return true;
151
        }
152
153 1
        if ($inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->hasMethod($name, true)) {
154
            $method = $this->extendsClassDefinition->getMethod($name);
155
            return $method->isPublic() || $method->isProtected();
156
        }
157
158 1
        return false;
159
    }
160
161
    /**
162
     * @param $name
163
     * @return bool
164
     */
165
    public function hasConst($name)
166
    {
167
        return isset($this->constants[$name]);
168
    }
169
170
    /**
171
     * @param $name
172
     * @param boolean|false $inherit
173
     * @return ClassMethod
174
     */
175 1
    public function getMethod($name, $inherit = false)
176
    {
177 1
        if (isset($this->methods[$name])) {
178 1
            return $this->methods[$name];
179
        }
180
181
        return $inherit && $this->extendsClassDefinition && $this->extendsClassDefinition->getMethod($name, true);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $inherit && $this...getMethod($name, true); (boolean) is incompatible with the return type documented by PHPSA\Definition\ClassDefinition::getMethod of type PHPSA\Definition\ClassMethod.

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...
182
    }
183
184
    /**
185
     * @param $name
186
     * @return bool
187
     */
188 1
    public function hasProperty($name)
189
    {
190 1
        return isset($this->properties[$name]);
191
    }
192
193
    /**
194
     * @param $name
195
     * @return CompiledExpression
196
     */
197
    public function getProperty($name)
198
    {
199
        assert($this->hasProperty($name));
200
        return $this->properties[$name];
201
    }
202
203
    /**
204
     * @return string
205
     */
206
    public function getFilepath()
207
    {
208
        return $this->filepath;
209
    }
210
211
    /**
212
     * @param string $filepath
213
     */
214
    public function setFilepath($filepath)
215
    {
216
        $this->filepath = $filepath;
217
    }
218
219
    /**
220
     * @return bool
221
     */
222
    public function isAbstract()
223
    {
224
        return (bool) ($this->type & Node\Stmt\Class_::MODIFIER_ABSTRACT);
225
    }
226
227
    /**
228
     * @param null|string $extendsClass
229
     */
230
    public function setExtendsClass($extendsClass)
231
    {
232
        $this->extendsClass = $extendsClass;
233
    }
234
235
    /**
236
     * @return null|ClassDefinition
237
     */
238
    public function getExtendsClassDefinition()
239
    {
240
        return $this->extendsClassDefinition;
241
    }
242
243
    /**
244
     * @param ClassDefinition $extendsClassDefinition
245
     */
246
    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...
247
    {
248
        $this->extendsClassDefinition = $extendsClassDefinition;
249
    }
250
251
    /**
252
     * @param array $interface
253
     */
254
    public function addInterface($interface)
255
    {
256
        $this->interfaces[] = $interface;
257
    }
258
259
    /**
260
     * @return null|string
261
     */
262
    public function getExtendsClass()
263
    {
264
        return $this->extendsClass;
265
    }
266
}
267