Passed
Push — master ( 190321...e3e57c )
by Fran
12:29
created

Dto   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Test Coverage

Coverage 94.32%

Importance

Changes 12
Bugs 0 Features 0
Metric Value
eloc 88
c 12
b 0
f 0
dl 0
loc 194
ccs 83
cts 88
cp 0.9432
rs 8.8798
wmc 44

10 Methods

Rating   Name   Duplication   Size   Complexity  
B __toArray() 0 28 10
A castValue() 0 3 1
B parseDtoField() 0 25 9
A fromArray() 0 9 5
A extractTypes() 0 12 3
A toArray() 0 3 1
A __construct() 0 5 2
A jsonSerialize() 0 3 1
A __toString() 0 3 1
B checkCastedValue() 0 33 11

How to fix   Complexity   

Complex Class

Complex classes like Dto 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 Dto, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PSFS\base\dto;
4
5
use PSFS\base\Logger;
6
use PSFS\base\Request;
7
use PSFS\base\Singleton;
8
use PSFS\base\types\helpers\I18nHelper;
9
use PSFS\base\types\helpers\InjectorHelper;
10
11
/**
12
 * Class Dto
13
 * @package PSFS\base\dto
14
 */
15
class Dto extends Singleton implements \JsonSerializable
16
{
17
    /**
18
     * @var array
19
     */
20
    protected array $__cache = [];
21
22 4
    public function __construct($hydrate = true)
23
    {
24 4
        parent::__construct();
25 4
        if ($hydrate) {
26 2
            $this->fromArray(Request::getInstance()->getData());
27
        }
28
    }
29
30
    /**
31
     * ToArray wrapper
32
     * @return array
33
     */
34 4
    public function toArray()
35
    {
36 4
        return $this->__toArray();
37
    }
38
39
    /**
40
     * Convert dto to array representation
41
     * @return array
42
     */
43 4
    public function __toArray()
44
    {
45 4
        $dto = [];
46
        try {
47 4
            $reflectionClass = new \ReflectionClass($this);
48 4
            $properties = $reflectionClass->getProperties(\ReflectionProperty::IS_PUBLIC);
49 4
            if (count($properties) > 0) {
50 4
                foreach ($properties as $property) {
51 4
                    $value = $property->getValue($this);
52 4
                    if (is_object($value) && method_exists($value, 'toArray')) {
53 1
                        $dto[$property->getName()] = $value->toArray();
54 4
                    } elseif (is_array($value)) {
55 4
                        foreach ($value as &$arrValue) {
56 2
                            if ($arrValue instanceof Dto) {
57 1
                                $arrValue = $arrValue->toArray();
58
                            }
59
                        }
60 4
                        $dto[$property->getName()] = $value;
61
                    } else {
62 4
                        $type = InjectorHelper::extractVarType($property->getDocComment());
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $type is correct as PSFS\base\types\helpers\...perty->getDocComment()) targeting PSFS\base\types\helpers\...elper::extractVarType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
63 4
                        $dto[$property->getName()] = $this->checkCastedValue($property->getValue($this), $type ?: 'string');
64
                    }
65
                }
66
            }
67
        } catch (\Exception $e) {
68
            Logger::log(get_class($this) . ': ' . $e->getMessage(), LOG_ERR);
69
        }
70 4
        return $dto;
71
    }
72
73
    /**
74
     * Convert to string representation
75
     * @return string
76
     */
77 1
    public function __toString()
78
    {
79 1
        return get_class($this);
80
    }
81
82
    /**
83
     * @param array $properties
84
     * @param string $key
85
     * @param mixed|null $value
86
     */
87 2
    protected function parseDtoField(array $properties, string $key, $value = null)
88
    {
89 2
        list($type, $isArray) = $this->extractTypes($properties, $key);
90 2
        $reflector = (class_exists($type)) ? new \ReflectionClass($type) : null;
91 2
        if (null !== $reflector && $reflector->isSubclassOf(Dto::class)) {
92 1
            if (is_array($value)) {
93 1
                if (!array_key_exists($type, $this->__cache)) {
94 1
                    $this->__cache[$type] = new $type(false);
95
                }
96 1
                if ($isArray) {
97 1
                    $this->$key = [];
98 1
                    foreach ($value as $data) {
99 1
                        if (is_array($data)) {
100 1
                            $dto = clone $this->__cache[$type];
101 1
                            $dto->fromArray($data);
102 1
                            $this->$key[] = $dto;
103
                        }
104
                    }
105
                } else {
106 1
                    $this->$key = clone $this->__cache[$type];
107 1
                    $this->$key->fromArray($value);
108
                }
109
            }
110
        } else {
111 2
            $this->castValue($key, $value, $type);
112
        }
113
    }
114
115
    /**
116
     * Hydrate object from array
117
     * @param array $object
118
     * @throws \ReflectionException
119
     */
120 3
    public function fromArray(array $object = [])
121
    {
122 3
        if (count($object) > 0) {
123 2
            $reflector = new \ReflectionClass($this);
124 2
            $properties = InjectorHelper::extractProperties($reflector, \ReflectionProperty::IS_PUBLIC, InjectorHelper::VAR_PATTERN);
125 2
            unset($reflector);
126 2
            foreach ($object as $key => $value) {
127 2
                if (property_exists($this, $key) && null !== $value) {
128 2
                    $this->parseDtoField($properties, $key, $value);
129
                }
130
            }
131
        }
132
    }
133
134
    /**
135
     * @return mixed
136
     */
137 1
    public function jsonSerialize(): array|string|null
138
    {
139 1
        return $this->toArray();
140
    }
141
142
    /**
143
     * @param array $properties
144
     * @param string $key
145
     * @return array
146
     */
147 2
    protected function extractTypes(array $properties, string $key): array
148
    {
149 2
        $type = 'string';
150 2
        $isArray = false;
151 2
        if (array_key_exists($key, $properties)) {
152 2
            $type = $properties[$key];
153 2
            if (preg_match('/(\[\]|Array)/i', $type)) {
154 1
                $type = preg_replace('/(\[\]|Array)/i', '', $type);
155 1
                $isArray = true;
156
            }
157
        }
158 2
        return array($type, $isArray);
159
    }
160
161
    /**
162
     * @param string $key
163
     * @param mixed $value
164
     * @param string $type
165
     */
166 2
    protected function castValue(string $key, mixed $value, string $type): void
167
    {
168 2
        $this->$key = $this->checkCastedValue($value, $type);
169
    }
170
171
    /**
172
     * @param mixed $rawValue
173
     * @param string $type
174
     * @return bool|float|int|string|null
175
     */
176 4
    protected function checkCastedValue(mixed $rawValue, string $type)
177
    {
178 4
        if (null !== $rawValue) {
179
            switch ($type) {
180
                default:
181 4
                case 'string':
182
                    // Cleaning HTML dangerous tags
183 4
                    if(is_array($rawValue)) {
184
                        foreach($rawValue as &$item) {
185
                            $item = $this->checkCastedValue($item, $type);
186
                        }
187
                        $value = $rawValue;
188
                    } else {
189 4
                        $value = I18nHelper::cleanHtmlAttacks($rawValue);
190
                    }
191 4
                    break;
192 4
                case 'integer':
193 4
                case 'int':
194 2
                    $value = (integer)$rawValue;
195 2
                    break;
196 4
                case 'float':
197 4
                case 'double':
198 1
                    $value = (float)$rawValue;
199 1
                    break;
200 4
                case 'boolean':
201 3
                case 'bool':
202 4
                    $value = (bool)$rawValue;
203 4
                    break;
204
            }
205
        } else {
206 2
            $value = $rawValue;
207
        }
208 4
        return $value;
209
    }
210
}
211