Passed
Push — main ( f5bbd1...48bf18 )
by Serhii
01:49
created

BaseObject   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 434
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 114
c 3
b 0
f 0
dl 0
loc 434
rs 3.44
wmc 62

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setProperty() 0 3 1
A has() 0 3 1
A isPrimitive() 0 3 1
A getListProperties() 0 3 1
A isList() 0 3 1
A getPrimitiveProperties() 0 3 1
A __call() 0 14 4
A isObject() 0 10 5
A __get() 0 3 1
A getProperty() 0 6 2
A getObjectProperties() 0 3 1
A fromJsonString() 0 4 1
A __isset() 0 3 1
A getObjectPropertyNames() 0 3 1
A toArray() 0 3 1
A getPrimitivePropertyNames() 0 3 1
A getPropertiesByNames() 0 7 2
A jsonSerialize() 0 3 1
A getError() 0 3 2
A __construct() 0 22 6
A getData() 0 3 1
A hasErrors() 0 3 2
A filterProperties() 0 14 4
B fromArray() 0 35 7
A getListPropertyNames() 0 3 1
A getFieldName() 0 12 3
A __set() 0 3 1
A getPropertyNames() 0 3 1
A convertFieldName() 0 16 5
A _has() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like BaseObject 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.

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 BaseObject, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace EndorphinStudio\DataObject;
4
5
/**
6
 * BaseObject
7
 * @package EndorphinStudio\DataObject
8
 * @author Serhii Nekhaienko <[email protected]>
9
 */
10
abstract class BaseObject implements \JsonSerializable
11
{
12
    public const CAMEL_CASE = '';
13
    public const SNAKE_LASE = '_';
14
15
    /**
16
     * @var array $data
17
     * Array of data fields with data
18
     */
19
    protected array $data;
20
21
    /**
22
     * @var string[]
23
     * Define mappings of fields
24
     */
25
    protected array $fieldTypeMapping = [
26
27
    ];
28
29
    /**
30
     * @var string[]
31
     * Define fields which are list of objects
32
     */
33
    protected array $listFields = [
34
35
    ];
36
37
    /**
38
     * @var string[]
39
     * List of primitives
40
     */
41
    protected array $primitiveTypes = [
42
        'int',
43
        'integer',
44
        'string',
45
        'boolean',
46
        'double',
47
        'string',
48
    ];
49
50
    public function __construct(array $data = [])
51
    {
52
        if (!is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
53
            throw new \RuntimeException('$data should be an array');
54
        }
55
        $this->data = $data;
56
57
        foreach ($this->data as $key => $value) {
58
            if (is_array($value)) {
59
                $this->setProperty($key, $this->fromArray($key, $value));
60
            }
61
        }
62
63
        /**
64
         * Call init$PropertyName if method exist
65
         */
66
        foreach (array_keys($this->data) as $option) {
67
            $key = $this->convertFieldName($option);
68
69
            $method = 'init' . ucfirst($key);
70
            if (method_exists($this, $method)) {
71
                $this->$method();
72
            }
73
        }
74
    }
75
76
    /**
77
     * @param string $field
78
     * @param array $value
79
     * @return DataObject|mixed|BaseObject
80
     */
81
    private function fromArray(string $field, array $value)
82
    {
83
        /**
84
         * Check if this field has type mapping
85
         */
86
        if (array_key_exists($field, $this->fieldTypeMapping)) {
87
            $className = $this->fieldTypeMapping[$field];
88
            if (class_exists($className)) {
89
                return new $className($value);
90
            }
91
        }
92
93
        /**
94
         * Check if this field is list of objects
95
         */
96
        if (array_key_exists($field, $this->listFields)) {
97
            $className = $this->listFields[$field];
98
99
            /**
100
             * Check if class is primitive (int, string, etc.)
101
             */
102
            if (in_array($className, $this->primitiveTypes, true)) {
103
                return $value;
104
            }
105
106
            if (class_exists($className)) {
107
                $list = [];
108
                foreach ($value as $item) {
109
                    $list[] = new $className($item);
110
                }
111
                return $list;
112
            }
113
        }
114
115
        return new DataObject($value);
116
    }
117
118
    /**
119
     * @param $name
120
     * @return mixed|null
121
     * Return object
122
     */
123
    public function __get($name)
124
    {
125
        return $this->data[$name] ?? null;
126
    }
127
128
    /**
129
     * @param $name
130
     * @param $value
131
     * Set object
132
     */
133
    public function __set($name, $value)
134
    {
135
        $this->data[$name] = $value;
136
    }
137
138
    /**
139
     * @param $name
140
     * @return bool
141
     * Check if field exist
142
     */
143
    public function __isset($name): bool
144
    {
145
        return $this->_has($name);
146
    }
147
148
    /**
149
     * @param $name
150
     * @return bool
151
     * Check if field exist
152
     */
153
    private function _has($name): bool
154
    {
155
        return array_key_exists(
156
                $name,
157
                $this->data
158
            ) && $this->data[$name];
159
    }
160
161
    /**
162
     * @param $name
163
     * @param $arguments
164
     * @return boolean|mixed|null
165
     */
166
    public function __call($name, $arguments)
167
    {
168
        $words = preg_split('/(?=[A-Z])/', $name);
169
        $methodType = $words[0];
170
        unset($words[0]);
171
        $field_name = $this->getFieldName(implode('', $words));
172
        switch ($methodType) {
173
            case 'get':
174
                return $this->$field_name;
175
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
176
            case 'has':
177
            case 'is':
178
                return $this->_has($field_name);
179
                break;
180
        }
181
    }
182
183
    /**
184
     * Return field name for data
185
     * @param $name
186
     * @return string
187
     */
188
    private function getFieldName($name): string
189
    {
190
        $name = lcfirst($name);
191
        $camelName = $this->convertFieldName($name, self::CAMEL_CASE);
192
        $snakeName = $this->convertFieldName($name, self::SNAKE_LASE);
193
        if (array_key_exists($camelName, $this->data)) {
194
            return $camelName;
195
        }
196
        if (array_key_exists($snakeName, $this->data)) {
197
            return $snakeName;
198
        }
199
        return $name;
200
    }
201
202
    /**
203
     * Convert MyField to my_field or my_field to MyField
204
     * @param string $name
205
     * @param string $replacement
206
     * @return string
207
     */
208
    private function convertFieldName($name, $replacement = ''): string
209
    {
210
        $words = preg_split('/(?=[A-Z_])/', $name);
211
        foreach ($words as &$word) {
212
            $word = str_replace('_', '', $word);
213
        }
214
        if (!empty($replacement)) {
215
            return strtolower(implode($replacement, $words));
216
        }
217
        foreach ($words as $i => &$word) {
218
            if ($i === 0) {
219
                continue;
220
            }
221
            $word = ucfirst($word);
222
        }
223
        return implode($replacement, $words);
224
    }
225
226
    /**
227
     * @param $key
228
     * @return bool
229
     * Check that fieldExist
230
     */
231
    public function has($key): bool
232
    {
233
        return $this->_has($key);
234
    }
235
236
    /**
237
     * @return bool
238
     * Check if errors exist in object
239
     */
240
    public function hasErrors(): bool
241
    {
242
        return $this->has('error') || $this->has('errors');
243
    }
244
245
    /**
246
     * @return string
247
     * Return error
248
     */
249
    public function getError(): string
250
    {
251
        return ($this->has('error')) ? $this->data['error'] : $this->data['errors'];
252
    }
253
254
    /**
255
     * @return array
256
     * Return all fields
257
     */
258
    public function getData(): array
259
    {
260
        return $this->data;
261
    }
262
263
    /**
264
     * @param string $propertyName
265
     * @return mixed|null
266
     * Return field
267
     */
268
    public function getProperty(string $propertyName)
269
    {
270
        if ($this->has($propertyName)) {
271
            return $this->$propertyName;
272
        }
273
        return null;
274
    }
275
276
    /**
277
     * @return array
278
     * Used during jsonSerialization
279
     */
280
    public function jsonSerialize(): array
281
    {
282
        return $this->toArray();
283
    }
284
285
    /**
286
     * @param string $propertyName
287
     * @param $value
288
     * Set field
289
     */
290
    public function setProperty(string $propertyName, $value): void
291
    {
292
        $this->data[$propertyName] = $value;
293
    }
294
295
    /**
296
     * @return array
297
     * Return array with data
298
     */
299
    public function toArray(): array
300
    {
301
        return $this->getData();
302
    }
303
304
    /**
305
     * @param string $json
306
     * @return BaseObject
307
     * @throws \JsonException
308
     * Create object from JSON string
309
     */
310
    public static function fromJsonString(string $json): BaseObject
311
    {
312
        $jsonObject = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
313
        return new static($jsonObject);
314
    }
315
316
    /**
317
     * @return array
318
     * Return array with names of properties
319
     */
320
    public function getPropertyNames(): array
321
    {
322
        return array_keys($this->toArray());
323
    }
324
325
    /**
326
     * @param string $propertyName
327
     * @return bool
328
     * Return true if property is list or array
329
     */
330
    public function isList(string $propertyName): bool
331
    {
332
        return is_array($this->getProperty($propertyName));
333
    }
334
335
    /**
336
     * @param string $propertyName
337
     * @return bool
338
     * Return true if property is object or list of object
339
     */
340
    public function isObject(string $propertyName): bool
341
    {
342
        $value = $this->getProperty($propertyName);
343
        if (is_object($value)) {
344
            return true;
345
        }
346
        if (in_array($propertyName, $this->listFields, true) && $this->isList($propertyName) && is_object($value[0])) {
347
            return true;
348
        }
349
        return false;
350
    }
351
352
    /**
353
     * @param string $propertyName
354
     * @return bool
355
     * Return true if property has primitive type (string, integer, boolean)
356
     */
357
    public function isPrimitive(string $propertyName): bool
358
    {
359
        return in_array(gettype($this->getProperty($propertyName)), $this->primitiveTypes, true);
360
    }
361
362
    protected function filterProperties(string $filterFunction): array
363
    {
364
        if (!method_exists($this, $filterFunction)) {
365
            return [];
366
        }
367
368
        $properties = $this->getPropertyNames();
369
        $result = [];
370
        foreach ($properties as $property) {
371
            if($this->$filterFunction($property)) {
372
                $result[] = $property;
373
            }
374
        }
375
        return $result;
376
    }
377
378
    /**
379
     * @return array
380
     * Return property names which has primitive type (string, integer, boolean)
381
     */
382
    public function getPrimitivePropertyNames(): array
383
    {
384
        return $this->filterProperties('isPrimitive');
385
    }
386
387
    /**
388
     * @param array $names
389
     * @return array
390
     * Return array with desired properties and their values
391
     */
392
    protected function getPropertiesByNames(array $names): array
393
    {
394
        $result = [];
395
        foreach ($names as $property) {
396
            $result[$property] = $this->getProperty($property);
397
        }
398
        return $result;
399
    }
400
401
    /**
402
     * @return array
403
     * Return array with properties which has primitive type (integer, string, boolean)
404
     */
405
    public function getPrimitiveProperties(): array
406
    {
407
        return $this->getPropertiesByNames($this->getPrimitivePropertyNames());
408
    }
409
410
    /**
411
     * @return array
412
     * Return array with property names which are list or array
413
     */
414
    public function getListPropertyNames(): array
415
    {
416
        return $this->filterProperties('isList');
417
    }
418
419
    /**
420
     * @return array
421
     * Return array with property which are list or array and their values
422
     */
423
    public function getListProperties(): array
424
    {
425
        return $this->getPropertiesByNames($this->getListPropertyNames());
426
    }
427
428
    /**
429
     * @return array
430
     * Return array with property names which are objects or list of objects
431
     */
432
    public function getObjectPropertyNames(): array
433
    {
434
        return $this->filterProperties('isObject');
435
    }
436
437
    /**
438
     * @return array
439
     * Return array with properties which are objects or list of objects
440
     */
441
    public function getObjectProperties(): array
442
    {
443
        return $this->getPropertiesByNames($this->getObjectPropertyNames());
444
    }
445
}
446