Completed
Push — master ( 89795a...454e04 )
by Marco
07:56 queued 07:53
created

ReflectionProperty::hasType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\BetterReflection\Reflection;
6
7
use Closure;
8
use Exception;
9
use InvalidArgumentException;
10
use phpDocumentor\Reflection\Type;
11
use PhpParser\Node;
12
use PhpParser\Node\NullableType;
13
use PhpParser\Node\Stmt\Class_;
14
use PhpParser\Node\Stmt\Namespace_;
15
use PhpParser\Node\Stmt\Property as PropertyNode;
16
use ReflectionException;
17
use ReflectionProperty as CoreReflectionProperty;
18
use Reflector as CoreReflector;
19
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
20
use Roave\BetterReflection\NodeCompiler\CompilerContext;
21
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
22
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
23
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
24
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
25
use Roave\BetterReflection\Reflection\Exception\Uncloneable;
26
use Roave\BetterReflection\Reflection\StringCast\ReflectionPropertyStringCast;
27
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
28
use Roave\BetterReflection\Reflector\Reflector;
29
use Roave\BetterReflection\TypesFinder\FindPropertyType;
30
use Roave\BetterReflection\Util\CalculateReflectionColum;
31
use Roave\BetterReflection\Util\GetFirstDocComment;
32
use function class_exists;
33
use function func_num_args;
34
use function get_class;
35
use function is_object;
36
37
class ReflectionProperty implements CoreReflector
38
{
39
    /** @var ReflectionClass */
40
    private $declaringClass;
41
42
    /** @var ReflectionClass */
43
    private $implementingClass;
44
45
    /** @var PropertyNode */
46
    private $node;
47
48
    /** @var int */
49
    private $positionInNode;
50
51
    /** @var Namespace_|null */
52
    private $declaringNamespace;
53
54
    /** @var bool */
55
    private $declaredAtCompileTime = true;
56
57
    /** @var Reflector */
58 52
    private $reflector;
59
60 52
    private function __construct()
61
    {
62 1
    }
63
64 1
    public static function export() : void
65
    {
66
        throw new Exception('Unable to export statically');
67
    }
68
69
    /**
70 1
     * Create a reflection of a class's property by its name
71
     */
72 1
    public static function createFromName(string $className, string $propertyName) : self
73
    {
74
        return ReflectionClass::createFromName($className)->getProperty($propertyName);
75
    }
76
77
    /**
78
     * Create a reflection of an instance's property by its name
79
     *
80
     * @param object $instance
81
     *
82
     * @throws InvalidArgumentException
83
     * @throws ReflectionException
84 1
     * @throws IdentifierNotFound
85
     */
86 1
    public static function createFromInstance($instance, string $propertyName) : self
87
    {
88
        return ReflectionClass::createFromInstance($instance)->getProperty($propertyName);
89 1
    }
90
91 1
    public function __toString() : string
92
    {
93
        return ReflectionPropertyStringCast::toString($this);
94
    }
95
96
    /**
97
     * @internal
98
     *
99 52
     * @param PropertyNode $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
100
     */
101
    public static function createFromNode(
102
        Reflector $reflector,
103
        PropertyNode $node,
104
        int $positionInNode,
105
        ?Namespace_ $declaringNamespace,
106
        ReflectionClass $declaringClass,
107
        ReflectionClass $implementingClass,
108 52
        bool $declaredAtCompileTime = true
109 52
    ) : self {
110 52
        $prop                        = new self();
111 52
        $prop->reflector             = $reflector;
112 52
        $prop->node                  = $node;
113 52
        $prop->positionInNode        = $positionInNode;
114 52
        $prop->declaringNamespace    = $declaringNamespace;
115 52
        $prop->declaringClass        = $declaringClass;
116
        $prop->implementingClass     = $implementingClass;
117 52
        $prop->declaredAtCompileTime = $declaredAtCompileTime;
118
119
        return $prop;
120
    }
121
122
    /**
123
     * Set the default visibility of this property. Use the core \ReflectionProperty::IS_* values as parameters, e.g.:
124
     *
125 2
     * @throws InvalidArgumentException
126
     */
127 2
    public function setVisibility(int $newVisibility) : void
128
    {
129 2
        $this->node->flags &= ~Class_::MODIFIER_PRIVATE & ~Class_::MODIFIER_PROTECTED & ~Class_::MODIFIER_PUBLIC;
130
131 1
        switch ($newVisibility) {
132 1
            case CoreReflectionProperty::IS_PRIVATE:
133
                $this->node->flags |= Class_::MODIFIER_PRIVATE;
134 1
                break;
135 1
            case CoreReflectionProperty::IS_PROTECTED:
136
                $this->node->flags |= Class_::MODIFIER_PROTECTED;
137 1
                break;
138 1
            case CoreReflectionProperty::IS_PUBLIC:
139
                $this->node->flags |= Class_::MODIFIER_PUBLIC;
140 1
                break;
141
            default:
142 1
                throw new InvalidArgumentException('Visibility should be \ReflectionProperty::IS_PRIVATE, ::IS_PROTECTED or ::IS_PUBLIC constants');
143
        }
144
    }
145
146
    /**
147
     * Has the property been declared at compile-time?
148
     *
149
     * Note that unless the property is static, this is hard coded to return
150
     * true, because we are unable to reflect instances of classes, therefore
151 3
     * we can be sure that all properties are always declared at compile-time.
152
     */
153 3
    public function isDefault() : bool
154
    {
155
        return $this->declaredAtCompileTime;
156
    }
157
158
    /**
159 4
     * Get the core-reflection-compatible modifier values.
160
     */
161 4
    public function getModifiers() : int
162 4
    {
163 4
        $val  = 0;
164 4
        $val += $this->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
165 4
        $val += $this->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
166
        $val += $this->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
167 4
        $val += $this->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;
168
169
        return $val;
170
    }
171
172
    /**
173 51
     * Get the name of the property.
174
     */
175 51
    public function getName() : string
176
    {
177
        return $this->node->props[$this->positionInNode]->name->name;
178
    }
179
180
    /**
181 7
     * Is the property private?
182
     */
183 7
    public function isPrivate() : bool
184
    {
185
        return $this->node->isPrivate();
186
    }
187
188
    /**
189 7
     * Is the property protected?
190
     */
191 7
    public function isProtected() : bool
192
    {
193
        return $this->node->isProtected();
194
    }
195
196
    /**
197 6
     * Is the property public?
198
     */
199 6
    public function isPublic() : bool
200
    {
201
        return $this->node->isPublic();
202
    }
203
204
    /**
205 20
     * Is the property static?
206
     */
207 20
    public function isStatic() : bool
208
    {
209
        return $this->node->isStatic();
210
    }
211
212
    /**
213
     * Get the DocBlock type hints as an array of strings.
214
     *
215 3
     * @return string[]
216
     */
217 3
    public function getDocBlockTypeStrings() : array
218
    {
219 3
        $stringTypes = [];
220 3
221
        foreach ($this->getDocBlockTypes() as $type) {
222
            $stringTypes[] = (string) $type;
223 3
        }
224
225
        return $stringTypes;
226
    }
227
228
    /**
229
     * Get the types defined in the DocBlocks. This returns an array because
230
     * the parameter may have multiple (compound) types specified (for example
231
     * when you type hint pipe-separated "string|null", in which case this
232
     * would return an array of Type objects, one for string, one for null.
233
     *
234 6
     * @return Type[]
235
     */
236 6
    public function getDocBlockTypes() : array
237
    {
238
        return (new FindPropertyType())->__invoke($this, $this->declaringNamespace);
239 17
    }
240
241 17
    public function getDeclaringClass() : ReflectionClass
242
    {
243
        return $this->declaringClass;
244 3
    }
245
246 3
    public function getImplementingClass() : ReflectionClass
247
    {
248
        return $this->implementingClass;
249 9
    }
250
251 9
    public function getDocComment() : string
252
    {
253
        return GetFirstDocComment::forNode($this->node);
254
    }
255
256
    /**
257
     * Get the default value of the property (as defined before constructor is
258
     * called, when the property is defined)
259
     *
260 1
     * @return mixed
261
     */
262 1
    public function getDefaultValue()
263
    {
264 1
        $defaultValueNode = $this->node->props[$this->positionInNode]->default;
265 1
266
        if ($defaultValueNode === null) {
267
            return null;
268 1
        }
269 1
270 1
        return (new CompileNodeToValue())->__invoke(
271
            $defaultValueNode,
272
            new CompilerContext($this->reflector, $this->getDeclaringClass())
273
        );
274
    }
275
276
    /**
277 4
     * Get the line number that this property starts on.
278
     */
279 4
    public function getStartLine() : int
280
    {
281
        return $this->node->getStartLine();
282
    }
283
284
    /**
285 4
     * Get the line number that this property ends on.
286
     */
287 4
    public function getEndLine() : int
288
    {
289
        return $this->node->getEndLine();
290 3
    }
291
292 3
    public function getStartColumn() : int
293
    {
294
        return CalculateReflectionColum::getStartColumn($this->declaringClass->getLocatedSource()->getSource(), $this->node);
295 3
    }
296
297 3
    public function getEndColumn() : int
298
    {
299
        return CalculateReflectionColum::getEndColumn($this->declaringClass->getLocatedSource()->getSource(), $this->node);
300 7
    }
301
302 7
    public function getAst() : PropertyNode
303
    {
304
        return $this->node;
305 7
    }
306
307 7
    public function getPositionInAst() : int
308
    {
309
        return $this->positionInNode;
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     *
315 1
     * @throws Uncloneable
316
     */
317 1
    public function __clone()
318
    {
319
        throw Uncloneable::fromClass(self::class);
320
    }
321
322
    /**
323
     * @param object|null $object
324
     *
325
     * @return mixed
326
     *
327
     * @throws ClassDoesNotExist
328
     * @throws NoObjectProvided
329
     * @throws NotAnObject
330 9
     * @throws ObjectNotInstanceOfClass
331
     */
332 9
    public function getValue($object = null)
333
    {
334 9
        $declaringClassName = $this->getDeclaringClass()->getName();
335 4
336
        if ($this->isStatic()) {
337
            $this->assertClassExist($declaringClassName);
338 3
339 3
            return Closure::bind(function (string $declaringClassName, string $propertyName) {
340
                return $declaringClassName::${$propertyName};
341
            }, null, $declaringClassName)->__invoke($declaringClassName, $this->getName());
342 5
        }
343
344
        $instance = $this->assertObject($object);
345 2
346 2
        return Closure::bind(function ($instance, string $propertyName) {
347
            return $instance->{$propertyName};
348
        }, $instance, $declaringClassName)->__invoke($instance, $this->getName());
349
    }
350
351
    /**
352
     * @param object     $object
353
     * @param mixed|null $value
354
     *
355
     * @throws ClassDoesNotExist
356
     * @throws NoObjectProvided
357
     * @throws NotAnObject
358 9
     * @throws ObjectNotInstanceOfClass
359
     */
360 9
    public function setValue($object, $value = null) : void
361
    {
362 9
        $declaringClassName = $this->getDeclaringClass()->getName();
363 4
364
        if ($this->isStatic()) {
365
            $this->assertClassExist($declaringClassName);
366 3
367 3
            Closure::bind(function (string $declaringClassName, string $propertyName, $value) : void {
368
                $declaringClassName::${$propertyName} = $value;
369 3
            }, null, $declaringClassName)->__invoke($declaringClassName, $this->getName(), func_num_args() === 2 ? $value : $object);
370
371
            return;
372 5
        }
373
374
        $instance = $this->assertObject($object);
375 2
376 2
        Closure::bind(function ($instance, string $propertyName, $value) : void {
377 2
            $instance->{$propertyName} = $value;
378
        }, $instance, $declaringClassName)->__invoke($instance, $this->getName(), $value);
379
    }
380
381
    /**
382 5
     * Does this property allow null?
383
     */
384 5
    public function allowsNull() : bool
385 2
    {
386
        if (! $this->hasType()) {
387 3
            return true;
388
        }
389
390
        return $this->node->type instanceof NullableType;
391
    }
392
393
    /**
394
     * Get the ReflectionType instance representing the type declaration for
395
     * this property
396
     *
397
     * (note: this has nothing to do with DocBlocks).
398 8
     */
399
    public function getType() : ?ReflectionType
400 8
    {
401 2
        $type = $this->node->type;
402
403
        if ($type === null) {
404 6
            return null;
405 2
        }
406
407
        if ($type instanceof NullableType) {
408 4
            $type = $type->type;
409
        }
410 4
411 2
        return ReflectionType::createFromTypeAndReflector((string) $type, $this->allowsNull(), $this->reflector);
412
    }
413
414 2
    /**
415
     * Does this property have a type declaration?
416
     *
417
     * (note: this has nothing to do with DocBlocks).
418
     */
419
    public function hasType() : bool
420
    {
421
        return $this->node->type !== null;
422
    }
423
424
    /**
425
     * Set the property type declaration.
426
     */
427
    public function setType(string $newPropertyType) : void
428
    {
429
        $this->node->type = new Node\Name($newPropertyType);
430
    }
431
432
    /**
433
     * Remove the property type declaration completely.
434
     */
435
    public function removeType() : void
436
    {
437
        $this->node->type = null;
438
    }
439
440
    /**
441
     * @throws ClassDoesNotExist
442
     */
443
    private function assertClassExist(string $className) : void
444
    {
445
        if (! class_exists($className, false)) {
446
            throw new ClassDoesNotExist('Property cannot be retrieved as the class is not loaded');
447
        }
448
    }
449
450
    /**
451
     * @param mixed $object
452
     *
453
     * @return object
454
     *
455
     * @throws NoObjectProvided
456
     * @throws NotAnObject
457
     * @throws ObjectNotInstanceOfClass
458
     */
459
    private function assertObject($object)
460
    {
461
        if ($object === null) {
462
            throw NoObjectProvided::create();
463
        }
464
465
        if (! is_object($object)) {
466
            throw NotAnObject::fromNonObject($object);
467
        }
468
469
        $declaringClassName = $this->getDeclaringClass()->getName();
470
471
        if (get_class($object) !== $declaringClassName) {
472
            throw ObjectNotInstanceOfClass::fromClassName($declaringClassName);
473
        }
474
475
        return $object;
476
    }
477
}
478