Property::checkType()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 6
1
<?php declare(strict_types=1);
2
3
namespace Benrowe\Properties;
4
5
use Closure;
6
7
/**
8
 * Defines a unique property.
9
 * As a base, the property must have a a name. Additionally
10
 *
11
 * @package Benrowe\Properties
12
 * @todo add support for validating a property's value when being set
13
 */
14
class Property
15
{
16
    /**
17
     * @var string property name
18
     */
19
    private $name;
20
21
    /**
22
     * @var string|Closure|null the value type, {@see setType} for more details
23
     */
24
    private $type = null;
25
26
    /**
27
     * @var mixed the default value
28
     */
29
    private $default = null;
30
31
    /**
32 33
     * The currently set value
33
     */
34 33
    private $value = null;
35 33
36 33
    /**
37 33
     * @var Closure|string|null the setter mutator
38
     */
39
    private $setter;
40
41
    /**
42
     * @var Closure|string|null the getter mutator
43 33
     */
44
    private $getter;
45
46 33
    /**
47 33
     * @var string[] the base types the component will allow
48
     */
49
    const TYPES = [
50
            'string',
51
            'integer',
52
            'int',
53
            'float',
54 3
            'boolean',
55
            'bool',
56 3
            'array',
57
            'object',
58
            'null',
59
            'resource',
60
        ];
61
62
    const DOCBLOCK_PARAM_PATTERN = TypeChecker::DOCBLOCK_PARAM_PATTERN;
63
64
    /**
65 33
     * Create a new Property Instance
66
     *
67 33
     * @param string $name the name of the property
68
     * @param string|Closure|null $type {@see setType}
69 30
     * @param string|null $default the default value
70 30
     */
71
    public function __construct(string $name, $type = null, $default = null)
72
    {
73
        $this->setName($name);
74 6
        $this->setType($type);
75
        $this->setDefault($default);
76
    }
77
78
    /**
79
     * Set the property name
80
     *
81
     * @param string $name the name of the property
82
     */
83 6
    public function setName(string $name)
84 6
    {
85
        $this->name = $name;
86
    }
87 6
88 6
    /**
89
     * Get the property name
90
     *
91
     * @return string
92
     */
93
    public function getName(): string
94 3
    {
95
        return $this->name;
96 3
    }
97
98
    /**
99
     * Set the type for the property
100
     * The type acts as a validator for when the {@see setValue} is called.
101
     * Properties are strict so the type specified here must be exact
102
     *
103
     * The following types are supported:
104 33
     * - php primitative types {@see self::TYPES} for a list
105
     * - docblock style string
106 33
     * - fully quantified class name of instanceof
107 33
     * - Closure: bool determines if the value is acceptable
108
     * - null. no set checking, effectively treated as 'mixed'
109
     *
110
     * @param string|Closure|null $type
111
     * @return void
112
     * @throws PropertyException if type is unsupported
113
     */
114 3
    public function setType($type): void
115
    {
116 3
        // null/callable
117
        if (is_callable($type) || $type === null) {
118
            $this->type = $type;
119
            return;
120
        }
121
122
        // primitaves
123
        if (in_array(strtolower($type), self::TYPES, true)) {
124 12
            $this->type = strtolower($type);
125
            return;
126 12
        }
127 6
128
        if (preg_match(self::DOCBLOCK_PARAM_PATTERN, $type)) {
129 12
            $this->type = $type;
130 12
            return;
131
        }
132
133
        // unknown, drop
134
        throw new PropertyException(PropertyException::UNKNOWN_TYPE);
135
    }
136
137 15
    /**
138
     * Get the property type
139 15
     * @return closure|string|null
140 9
     */
141
    public function getType()
142 12
    {
143 12
        return $this->type;
144 3
    }
145
146
    /**
147 12
     * Set the default value of the property if nothing is explicitly set
148
     *
149
     * @param mixed $default
150
     */
151
    public function setDefault($default)
152
    {
153
        $this->default = $default;
154
    }
155
156
    /**
157 6
     * Get the default value
158
     *
159 6
     * @return mixed
160
     */
161 6
    public function getDefault()
162
    {
163
        return $this->default;
164
    }
165
166
    /**
167
     * Set the value against the property
168
     *
169
     * @param mixed $value
170
     */
171 3
    public function setValue($value)
172
    {
173 3
        if ($this->setter) {
174
            $value = call_user_func($this->setter, $value);
175 3
        }
176
        // check the value against the type specified
177
        if ($this->type !== null && !$this->checkType($this->type, $value)) {
178
            throw new PropertyException("Value specified for \"{$this->name}\" is not of the correct type");
179
        }
180
        $this->value = $value;
181
    }
182
183
    /**
184
     * Check the the value against the type and see if we have a match
185
     *
186
     * @param string|Closure $type the type
187
     * @param mixed $value the value to check
188
     *
189
     * @return bool
190
     */
191
    private function checkType($type, $value): bool
192
    {
193
        $checker = new TypeChecker($value);
194
195
        $method = is_callable($type) ? 'checkAsClosure' : 'check';
196
197
        return \call_user_func([$checker, $method], $type);
198
    }
199
200
201
    /**
202
     * Get the currently set value, if no value is set the default is used
203
     *
204
     * @param mixed $default runtime default value. specify the default value for this
205
     *                       property when you call this method
206
     * @return mixed
207
     */
208
    public function getValue($default = null)
209
    {
210
        if ($this->value === null) {
211
            return $default !== null ? $default : $this->default;
212
        }
213
        $value = $this->value;
214
        if ($this->getter) {
215
            $value = call_user_func($this->getter, $value);
216
        }
217
218
        return $value;
219
    }
220
221
    /**
222
     * Register a closure to mutate the properties value before being stored.
223
     * This can be to cast the value to the $type specified
224
     *
225
     * @param  Closure $setter the custom function to run when the value is
226
     * being set
227
     * @return self
228
     */
229
    public function setter(Closure $setter)
230
    {
231
        $this->setter = $setter;
232
233
        return $this;
234
    }
235
236
    /**
237
     * Specify a custom closer to handle the retreival of the value stored
238
     * against this property
239
     *
240
     * @param  Closure $getter [description]
241
     * @return self
242
     */
243
    public function getter(Closure $getter)
244
    {
245
        $this->getter = $getter;
246
247
        return $this;
248
    }
249
}
250