Passed
Push — master ( 4ef44e...1abade )
by Arthur
15:00
created

DataTransferObject   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 90
dl 0
loc 283
rs 7.92
c 0
b 0
f 0
wmc 51

20 Methods

Rating   Name   Duplication   Size   Complexity  
A all() 0 9 2
A boot() 0 26 2
A determineImmutability() 0 11 4
A chainPropertyImmutable() 0 9 5
A setPropertyValue() 0 4 2
A mutateProperty() 0 3 1
A __get() 0 3 1
A __construct() 0 3 1
A immutable() 0 6 1
A __set() 0 13 4
A only() 0 5 1
A setPropertyDefaultValue() 0 3 1
A toArray() 0 16 5
A isImmutable() 0 3 1
A validateProperty() 0 8 5
A getPublicProperties() 0 10 2
A parseArray() 0 20 5
A except() 0 10 3
A setImmutable() 0 6 3
A processRemainingProperties() 0 4 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Larapie\DataTransferObject;
6
7
use Larapie\DataTransferObject\Contracts\AdditionalProperties;
0 ignored issues
show
Bug introduced by
The type Larapie\DataTransferObje...ts\AdditionalProperties was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use ReflectionClass;
9
use ReflectionProperty;
10
use Larapie\DataTransferObject\Contracts\immutable;
11
use Larapie\DataTransferObject\Contracts\DtoContract;
12
use Larapie\DataTransferObject\Contracts\PropertyContract;
13
use Larapie\DataTransferObject\Exceptions\ImmutableDtoException;
14
use Larapie\DataTransferObject\Exceptions\PropertyNotFoundDtoException;
15
use Larapie\DataTransferObject\Exceptions\ImmutablePropertyDtoException;
16
use Larapie\DataTransferObject\Exceptions\UnknownPropertiesDtoException;
17
use Larapie\DataTransferObject\Exceptions\UninitialisedPropertyDtoException;
18
19
/**
20
 * Class DataTransferObject.
21
 */
22
abstract class DataTransferObject implements DtoContract
23
{
24
    /** @var array */
25
    protected $onlyKeys = [];
26
27
    /** @var Property[] | array */
28
    protected $properties = [];
29
30
    /** @var bool */
31
    protected $immutable = false;
32
33
    public function __construct(array $parameters)
34
    {
35
        $this->boot($parameters);
36
    }
37
38
    /**
39
     * Boot the dto and process all parameters.
40
     * @param array $parameters
41
     * @throws \ReflectionException
42
     */
43
    protected function boot(array $parameters): void
44
    {
45
        foreach ($this->getPublicProperties() as $property) {
46
47
            /*
48
             * Do not change the order of the following methods.
49
             * External packages rely on this order.
50
             */
51
52
            $this->setPropertyDefaultValue($property);
53
54
            $property = $this->mutateProperty($property);
55
56
            $this->validateProperty($property, $parameters);
57
58
            $this->setPropertyValue($property, $parameters);
59
60
            /* add the property to an associative array with the name as key */
61
            $this->properties[$property->getName()] = $property;
62
63
            /* remove the property from the value object and parameters array  */
64
            unset($parameters[$property->getName()], $this->{$property->getName()});
65
        }
66
67
        $this->processRemainingProperties($parameters);
68
        $this->determineImmutability();
69
    }
70
71
    protected function determineImmutability()
72
    {
73
        if ($this instanceof immutable) {
74
            $this->setImmutable();
75
            return ;
76
        }
77
78
        /* If the dto itself is not immutable but some properties are chain them immutable  */
79
        foreach ($this->properties as $property) {
80
            if ($property->immutable())
81
                $this->chainPropertyImmutable($property);
82
        }
83
    }
84
85
    public function setImmutable(): void
86
    {
87
        if (!$this->isImmutable()) {
88
            $this->immutable = true;
89
            foreach ($this->properties as $property) {
90
                $this->chainPropertyImmutable($property);
91
            }
92
        }
93
    }
94
95
    protected function chainPropertyImmutable(PropertyContract $property)
96
    {
97
        $dto = $property->getValue();
98
        if ($dto instanceof DtoContract) {
99
            $dto->setImmutable();
100
        } elseif (is_iterable($dto)) {
101
            foreach ($dto as $aPotentialDto) {
102
                if ($aPotentialDto instanceof DtoContract) {
103
                    $aPotentialDto->setImmutable();
104
                }
105
            }
106
        }
107
    }
108
109
    /**
110
     * Get all public properties from the current object through reflection.
111
     * @return Property[]
112
     * @throws \ReflectionException
113
     */
114
    protected function getPublicProperties(): array
115
    {
116
        $class = new ReflectionClass(static::class);
117
118
        $properties = [];
119
        foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $reflectionProperty) {
120
            $properties[$reflectionProperty->getName()] = new Property($reflectionProperty);
121
        }
122
123
        return $properties;
124
    }
125
126
    /**
127
     * Check if property passes the basic conditions.
128
     * @param PropertyContract $property
129
     * @param array $parameters
130
     */
131
    protected function validateProperty(PropertyContract $property, array $parameters): void
132
    {
133
        if (!array_key_exists($property->getName(), $parameters)
134
            && is_null($property->getDefault())
135
            && !$property->nullable()
136
            && !$property->isOptional()
137
        ) {
138
            throw new UninitialisedPropertyDtoException($property);
139
        }
140
    }
141
142
    /**
143
     * Set the value if it's present in the array.
144
     * @param PropertyContract $property
145
     * @param array $parameters
146
     */
147
    protected function setPropertyValue(PropertyContract $property, array $parameters): void
148
    {
149
        if (array_key_exists($property->getName(), $parameters)) {
150
            $property->set($parameters[$property->getName()]);
151
        }
152
    }
153
154
    /**
155
     * Set the value if it's present in the array.
156
     * @param PropertyContract $property
157
     */
158
    protected function setPropertyDefaultValue(PropertyContract $property): void
159
    {
160
        $property->setDefault($property->getValueFromReflection($this));
161
    }
162
163
    /**
164
     * Allows to mutate the property before it gets processed.
165
     * @param PropertyContract $property
166
     * @return PropertyContract
167
     */
168
    protected function mutateProperty(PropertyContract $property): PropertyContract
169
    {
170
        return $property;
171
    }
172
173
    /**
174
     * Check if there are additional parameters left.
175
     * Throw error if there are.
176
     * Additional properties are not allowed in a dto.
177
     * @param array $parameters
178
     * @throws UnknownPropertiesDtoException
179
     */
180
    protected function processRemainingProperties(array $parameters)
181
    {
182
        if (count($parameters)) {
183
            throw new UnknownPropertiesDtoException(array_keys($parameters), static::class);
184
        }
185
    }
186
187
    /**
188
     * Immutable behavior
189
     * Throw error if a user tries to set a property.
190
     * @param $name
191
     * @param $value
192
     * @throws ImmutableDtoException|ImmutablePropertyDtoException|PropertyNotFoundDtoException
193
     */
194
    public function __set($name, $value)
195
    {
196
        if ($this->immutable) {
197
            throw new ImmutableDtoException($name);
198
        }
199
        if (!isset($this->properties[$name])) {
200
            throw new PropertyNotFoundDtoException($name, get_class($this));
201
        }
202
203
        if ($this->properties[$name]->immutable()) {
204
            throw new ImmutablePropertyDtoException($name);
205
        }
206
        $this->$name = $value;
207
    }
208
209
    /**
210
     * Proxy through to the properties array.
211
     * @param $name
212
     * @return mixed
213
     */
214
    public function &__get($name)
215
    {
216
        return $this->properties[$name]->value;
217
    }
218
219
    /**
220
     * @param array $data
221
     * @return static
222
     * @deprecated
223
     */
224
    public static function immutable(array $data): DtoContract
225
    {
226
        $dto = new static($data);
227
        $dto->setImmutable();
228
229
        return $dto;
230
    }
231
232
    public function isImmutable(): bool
233
    {
234
        return $this->immutable;
235
    }
236
237
    public function all(): array
238
    {
239
        $data = [];
240
241
        foreach ($this->properties as $property) {
242
            $data[$property->getName()] = $property->getValue();
243
        }
244
245
        return $data;
246
    }
247
248
    public function only(string ...$keys): DtoContract
249
    {
250
        $this->onlyKeys = array_merge($this->onlyKeys, $keys);
251
252
        return $this;
253
    }
254
255
    public function except(string ...$keys): DtoContract
256
    {
257
        foreach ($keys as $key) {
258
            $property = $this->properties[$key] ?? null;
259
            if (isset($property)) {
260
                $property->setVisible(false);
261
            }
262
        }
263
264
        return $this;
265
    }
266
267
    public function toArray(): array
268
    {
269
        $data = $this->all();
270
        $array = [];
271
272
        if (count($this->onlyKeys)) {
273
            $array = array_intersect_key($data, array_flip((array)$this->onlyKeys));
274
        } else {
275
            foreach ($data as $key => $propertyValue) {
276
                if ($this->properties[$key]->isVisible() && $this->properties[$key]->isInitialized()) {
277
                    $array[$key] = $propertyValue;
278
                }
279
            }
280
        }
281
282
        return $this->parseArray($array);
283
    }
284
285
    protected function parseArray(array $array): array
286
    {
287
        foreach ($array as $key => $value) {
288
            if (
289
                $value instanceof DataTransferObject
290
                || $value instanceof DataTransferObjectCollection
291
            ) {
292
                $array[$key] = $value->toArray();
293
294
                continue;
295
            }
296
297
            if (!is_array($value)) {
298
                continue;
299
            }
300
301
            $array[$key] = $this->parseArray($value);
302
        }
303
304
        return $array;
305
    }
306
}
307