Completed
Push — master ( 8950e2...73f5db )
by René
04:47
created

EntityProperty   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 7
dl 0
loc 282
ccs 107
cts 107
cp 1
rs 6.8
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 29 7
A setType() 0 8 2
D validateValue() 0 97 27
C formatValueForEntity() 0 38 12
C formatValueForDatabase() 0 32 7

How to fix   Complexity   

Complex Class

Complex classes like EntityProperty often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EntityProperty, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
4
namespace Zortje\MVC\Model\Table\Entity;
5
6
use Ramsey\Uuid\Uuid;
7
use Zortje\MVC\Model\Table\Entity\Exception\EntityPropertyTypeNonexistentException;
8
use Zortje\MVC\Model\Table\Entity\Exception\EntityPropertyTypeNotImplementedException;
9
use Zortje\MVC\Model\Table\Entity\Exception\EntityPropertyValueExceedingLengthException;
10
use Zortje\MVC\Model\Table\Entity\Exception\InvalidENUMValueForEntityPropertyException;
11
use Zortje\MVC\Model\Table\Entity\Exception\InvalidIPAddressValueForEntityPropertyException;
12
use Zortje\MVC\Model\Table\Entity\Exception\InvalidUUIDValueForEntityPropertyException;
13
use Zortje\MVC\Model\Table\Entity\Exception\InvalidValueTypeForEntityPropertyException;
14
15
/**
16
 * Class EntityProperty
17
 *
18
 * @package Zortje\MVC\Model\Table\Entity
19
 */
20
class EntityProperty
21
{
22
23
    const STRING = 'string';
24
    const INTEGER = 'integer';
25
    const FLOAT = 'float';
26
    const DOUBLE = 'double';
27
    const BOOL = 'bool';
28
29
    const DATE = 'date';
30
    const DATETIME = 'datetime';
31
32
    const IPADDRESS = 'ipaddress';
33
34
    const UUID = 'uuid';
35
36
    const ENUM = 'enum';
37
38
    /**
39
     * @var string Entity property type
40
     */
41
    protected $type;
42
43
    /**
44
     * @var int Entity property max length
45
     */
46
    protected $length;
47
48
    /**
49
     * @var array Allowed values
50
     */
51
    protected $values;
52
53
    /**
54
     * @param string|array $type
55
     */
56 13
    public function __construct($type)
57
    {
58 13
        if (is_array($type)) {
59
            /**
60
             * Type
61
             */
62 3
            if (!isset($type['type'])) {
63 1
                throw new \InvalidArgumentException('Index "type" not found in parameter array');
64
            }
65
66 2
            $this->setType($type['type']);
67
68
            /**
69
             * Length
70
             */
71 2
            if (isset($type['length']) && is_numeric($type['length'])) {
72 1
                $this->length = (int)$type['length'];
73
            }
74
75
            /**
76
             * Values
77
             */
78 2
            if (isset($type['values']) && is_array($type['values'])) {
79 2
                $this->values = array_fill_keys($type['values'], true);
80
            }
81
        } else {
82 10
            $this->setType($type);
83
        }
84 12
    }
85
86
    /**
87
     * Set entity property type
88
     *
89
     * @param string $type
90
     *
91
     * @throws EntityPropertyTypeNonexistentException
92
     */
93 13
    protected function setType(string $type)
94
    {
95 13
        if (!defined(EntityProperty::class . '::' . strtoupper($type))) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
96 1
            throw new EntityPropertyTypeNonexistentException([$type]);
97
        }
98
99 12
        $this->type = $type;
100 12
    }
101
102
    /**
103
     * Validate value for entity property
104
     *
105
     * @param mixed $value Entity property value
106
     *
107
     * @return bool TRUE if valid, otherwise FALSE
108
     *
109
     * @throws EntityPropertyTypeNotImplementedException If entity property type is not implemented
110
     * @throws EntityPropertyValueExceedingLengthException If value is exceeding allowed length for entity property
111
     * @throws InvalidENUMValueForEntityPropertyException If ENUM value is invalid for entity property
112
     * @throws InvalidUUIDValueForEntityPropertyException If UUID value is invalid for entity property
113
     * @throws InvalidValueTypeForEntityPropertyException If value is invalid for entity property
114
     * @throws InvalidIPAddressValueForEntityPropertyException If IP address value is invalid
115
     */
116 24
    public function validateValue($value): bool
117
    {
118 24
        if (is_null($value)) {
119 1
            return true;
120
        }
121
122 23
        switch ($this->type) {
123 23
            case self::STRING:
124 3
                if (!is_string($value)) {
125 1
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
126
                }
127
128
                /**
129
                 * Check length
130
                 */
131 2
                $length = strlen($value);
132
133 2
                if (!is_null($this->length) && $length > $this->length) {
134 1
                    throw new EntityPropertyValueExceedingLengthException([$value, $this->length]);
135
                }
136
137 1
                break;
138
139 20
            case self::INTEGER:
140 2
                if (!is_int($value)) {
141 1
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
142
                }
143
144 1
                break;
145
146 18
            case self::FLOAT:
147 2
                if (!is_float($value)) {
148 1
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
149
                }
150
151 1
                break;
152
153 16
            case self::DOUBLE:
154 2
                if (!is_double($value)) {
155 1
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
156
                }
157
158 1
                break;
159
160 14
            case self::BOOL:
161 2
                if (!is_bool($value)) {
162 1
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
163
                }
164
165 1
                break;
166
167 12
            case self::DATE:
168 10
            case self::DATETIME:
169 4
                if (!is_object($value) || (is_object($value) && get_class($value) !== \DateTime::class)) {
170 2
                    throw new InvalidValueTypeForEntityPropertyException([$this->type, gettype($value)]);
171
                }
172
173 2
                break;
174
175 8
            case self::IPADDRESS:
176 2
                if (filter_var($value, FILTER_VALIDATE_IP) === false) {
177 1
                    throw new InvalidIPAddressValueForEntityPropertyException([$value]);
178
                }
179
180 1
                break;
181
182 6
            case self::UUID:
183 3
                if (!is_string($value)) {
184 1
                    throw new InvalidValueTypeForEntityPropertyException(['string', gettype($value)]);
185
                }
186
187
                /**
188
                 * Check UUID
189
                 */
190 2
                if (!Uuid::isValid($value)) {
191 1
                    throw new InvalidUUIDValueForEntityPropertyException([$value]);
192
                }
193
194 1
                break;
195
196 3
            case self::ENUM:
197 3
                if (!is_string($value)) {
198 1
                    throw new InvalidValueTypeForEntityPropertyException(['string', gettype($value)]);
199
                }
200
201
                /**
202
                 * Check values
203
                 */
204 2
                if (!isset($this->values[$value])) {
205 1
                    throw new InvalidENUMValueForEntityPropertyException([$value]);
206
                }
207
208 1
                break;
209
        }
210
211 10
        return true;
212
    }
213
214
    /**
215
     * Format value according to entity property type
216
     *
217
     * @param mixed $value Value
218
     *
219
     * @return mixed Value
220
     *
221
     * @throws EntityPropertyTypeNotImplementedException If entity property type is not implemented
222
     */
223 8
    public function formatValueForEntity($value)
224
    {
225 8
        if (is_null($value)) {
226 1
            return null;
227
        }
228
229 7
        switch ($this->type) {
230 7
            case self::STRING:
231 6
            case self::UUID:
232 6
            case self::ENUM:
233 1
                $value = "$value";
234 1
                break;
235
236 6
            case self::INTEGER:
237 1
                $value = (int)$value;
238 1
                break;
239
240 5
            case self::FLOAT:
241 4
            case self::DOUBLE:
242 1
                $value = (float)$value;
243 1
                break;
244
245 4
            case self::DATE:
246 3
            case self::DATETIME:
247 2
                $value = new \DateTime($value);
248 2
                break;
249
250 2
            case self::IPADDRESS:
251 1
                $value = inet_ntop($value);
252 1
                break;
253
254 1
            case self::BOOL:
255 1
                $value = $value === '1';
256 1
                break;
257
        }
258
259 7
        return $value;
260
    }
261
262
    /**
263
     * Format value for insertion into the database
264
     *
265
     * @param mixed $value Value
266
     *
267
     * @return mixed Value
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|object|integer|double|string|array|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
268
     */
269 4
    public function formatValueForDatabase($value)
270
    {
271 4
        if (is_null($value)) {
272 4
            return null;
273
        }
274
275
        switch ($this->type) {
276 1
            case self::DATE:
277 1
                /**
278
                 * @var \DateTime $value
279 3
                 */
280
                $value = $value->format('Y-m-d');
281
                break;
282
283 1
            case self::DATETIME:
284 1
                /**
285
                 * @var \DateTime $value
286 2
                 */
287 1
                $value = $value->format('Y-m-d H:i:s');
288 1
                break;
289
290 1
            case self::BOOL:
291 1
                $value = $value ? '1' : '0';
292 1
                break;
293
294
            case self::IPADDRESS:
295 4
                $value = inet_pton($value);
296
                break;
297
        }
298
299
        return $value;
300
    }
301
}
302