Dto::__get()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Anfischer\Dto;
5
6
use Anfischer\Dto\Exceptions\InvalidTypeException;
7
use Anfischer\Dto\Exceptions\UndefinedPropertyException;
8
9
abstract class Dto
10
{
11
    protected const TYPE_SEPARATOR = '|';
12
13
    /**
14
     * Magic method for setting the properties of the DTO
15
     *
16
     * @param string $property
17
     * @param mixed $value
18
     * @throws InvalidTypeException
19
     * @throws UndefinedPropertyException
20
     */
21 206
    public function __set(string $property, $value): void
22
    {
23 206
        $type = $this->getPropertyType($property);
24
25 204
        if (self::validateCompoundTypes($type, $value)) {
26 44
            $this->$property = $value;
27
        } else {
28 176
            throw new InvalidTypeException(\get_class($this), $property, $type, self::getTypeOf($value));
29
        }
30 44
    }
31
32
    /**
33
     * Magic method for getting the properties of the DTO
34
     *
35
     * @param string $property
36
     * @return mixed
37
     * @throws UndefinedPropertyException
38
     */
39 48
    public function __get(string $property)
40
    {
41 48
        if ($this->__isset($property)) {
42 44
            return $this->$property;
43
        }
44
45 4
        throw new UndefinedPropertyException(\get_class($this), $property);
46
    }
47
48
    /**
49
     * Magic method for checking if a property is set
50
     *
51
     * @param $property
52
     * @return bool
53
     */
54 50
    public function __isset(string $property): bool
55
    {
56 50
        return property_exists($this, $property);
57
    }
58
59
    /**
60
     * Gets the property's type.
61
     * The method can be overridden per DTO basis to refine/restrict property types
62
     *
63
     * @param  string $property
64
     * @return string
65
     */
66 20
    public function getPropertyType($property): string
67
    {
68 20
        $value = $this->$property;
69
70 18
        if ($value === null) {
71
            // If value is not set, allow any value
72 18
            return 'mixed';
73
        }
74
75 16
        return self::getTypeOf($this->$property);
76
    }
77
78
    /**
79
     * Validates the properties types
80
     * Types can be separated by TYPE_SEPARATOR (default is pipe - e.g. string|null, integer|double)
81
     *
82
     * @param  string $compoundType
83
     * @param  mixed $value
84
     * @return bool
85
     */
86 204
    private static function validateCompoundTypes(string $compoundType, $value): bool
87
    {
88 204
        $types = \explode(static::TYPE_SEPARATOR, $compoundType);
89
90 204
        foreach ($types as $type) {
91 204
            if (self::validateType($type, $value)) {
92 204
                return true;
93
            }
94
        }
95
96 176
        return false;
97
    }
98
99
    /**
100
     * Validates a value against primitive types
101
     *
102
     * If no primitive implementation exists (an object is given) the function
103
     * checks if the object is of the property's class or has the property's class as one of its parents
104
     *
105
     * @param string $type
106
     * @param mixed $value
107
     * @return bool
108
     */
109 204
    private static function validateType(string $type, $value): bool
110
    {
111 204
        $typeClass = self::getTypeClassFor($type);
112 204
        if (class_exists($typeClass)) {
113 186
            return (new $typeClass())->isSatisfiedBy($value);
114
        }
115
116 18
        if (\is_a($value, $type)) {
117 2
            return true;
118
        }
119
120 16
        return false;
121
    }
122
123
    /**
124
     * @param string $type
125
     * @return string
126
     */
127 204
    private static function getTypeClassFor(string $type): string
128
    {
129 204
        return __NAMESPACE__ . '\\Types\\' . ucfirst($type) . 'Type';
130
    }
131
132
    /**
133
     * @param mixed $value
134
     * @return string
135
     */
136 176
    private static function getTypeOf($value): string
137
    {
138 176
        if (\is_object($value)) {
139 20
            return \get_class($value);
140
        }
141
142 158
        return \strtolower(\gettype($value));
143
    }
144
}
145