DataTransferObject::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Larapie\DataTransferObject;
6
7
use ReflectionException;
8
use Larapie\DataTransferObject\Property\Property;
9
use Larapie\DataTransferObject\Contracts\DtoContract;
10
use Larapie\DataTransferObject\Factories\PropertyFactory;
11
use Larapie\DataTransferObject\Exceptions\ValidatorException;
12
use Larapie\DataTransferObject\Exceptions\ImmutableDtoException;
13
use Larapie\DataTransferObject\Contracts\WithAdditionalProperties;
14
use Larapie\DataTransferObject\Exceptions\PropertyNotFoundDtoException;
15
use Larapie\DataTransferObject\Exceptions\ImmutablePropertyDtoException;
16
use Larapie\DataTransferObject\Exceptions\PropertyAlreadyExistsException;
17
18
/**
19
 * Class DataTransferObject.
20
 */
21
abstract class DataTransferObject implements DtoContract
22
{
23
    /** @var array */
24
    protected $onlyKeys = [];
25
26
    /** @var array */
27
    protected $with = [];
28
29
    /** @var Property[] */
30
    protected $properties = [];
31
32
    /** @var bool */
33
    protected $immutable = false;
34
35
    /** @var bool */
36
    protected $validate = true;
37
38 51
    public function __construct(array $parameters)
39
    {
40 51
        $this->boot($parameters);
41 47
    }
42
43
    /**
44
     * Boot the dto and process all parameters.
45
     * @param array $parameters
46
     * @throws ReflectionException
47
     */
48 51
    protected function boot(array $parameters): void
49
    {
50 51
        $this->properties = (new PropertyFactory($this))->build($parameters);
51 47
    }
52
53 6
    public function setImmutable(bool $immutable): void
54
    {
55 6
        if ($immutable) {
56 6
            if (! $this->isImmutable()) {
57 6
                $this->immutable = true;
58 6
                foreach ($this->properties as $property) {
59 6
                    $property->chainImmutable($immutable);
60
                }
61
            }
62
        } else {
63
            $this->immutable = true;
64
        }
65 6
    }
66
67
    /**
68
     * Immutable behavior
69
     * Throw error if a user tries to set a property.
70
     * @param $name
71
     * @param $value
72
     * @throws ImmutableDtoException|ImmutablePropertyDtoException|PropertyNotFoundDtoException
73
     */
74 7
    public function __set($name, $value)
75
    {
76 7
        if ($this->immutable) {
77 3
            throw new ImmutableDtoException($name);
78
        }
79 4
        if (! isset($this->properties[$name])) {
80 1
            throw new PropertyNotFoundDtoException($name, get_class($this));
81
        }
82
83 3
        if ($this->properties[$name]->isImmutable()) {
84 1
            throw new ImmutablePropertyDtoException($name);
85
        }
86 2
        $this->$name = $value;
87 2
    }
88
89 12
    protected function propertyExists(string $propertyName)
90
    {
91 12
        return array_key_exists($propertyName, $this->properties);
92
    }
93
94 12
    public function &__get($name)
95
    {
96 12
        if (! $this->propertyExists($name)) {
97 1
            if ($this instanceof WithAdditionalProperties) {
98 1
                if (array_key_exists($name, $this->with)) {
99 1
                    if ($this->isImmutable()) {
100
                        $value = $this->with[$name];
101
102
                        return clone $value;
103
                    }
104
105 1
                    return $this->with[$name];
106
                }
107
                throw new PropertyNotFoundDtoException($name, static::class);
108
            }
109
        }
110 11
        $property = $this->properties[$name];
111 11
        $violations = $property->getViolations();
112 11
        if ($violations->count() > 0) {
113
            throw new ValidatorException([$name => $violations]);
114
        }
115 11
        if ($this->isImmutable()) {
116 3
            $value = $this->properties[$name]->getValue();
117
118 3
            return $value;
119
        }
120
121 8
        return $this->properties[$name]->value;
122
    }
123
124 17
    public function isImmutable(): bool
125
    {
126 17
        return $this->immutable;
127
    }
128
129
    public function isValid()
130
    {
131
        return empty($this->getValidationViolations());
132
    }
133
134
    public function validationEnabled()
135
    {
136
        return $this->validate;
137
    }
138
139
    public function enableValidation()
140
    {
141
        $this->validate = true;
142
    }
143
144 10
    public function disableValidation()
145
    {
146 10
        $this->validate = false;
147 10
    }
148
149 19
    public function all(): array
150
    {
151 19
        $data = [];
152
153 19
        if ($this->validate) {
154 18
            $this->validate();
155
        }
156
157 19
        foreach ($this->properties as $property) {
158 17
            $data[$property->getName()] = ($value = $property->getValue()) instanceof DataTransferObject ? $value->toArray() : $value;
159
        }
160
161 19
        return array_merge($data, $this->with);
162
    }
163
164 1
    public function only(string ...$keys): DtoContract
165
    {
166 1
        $this->onlyKeys = array_merge($this->onlyKeys, $keys);
167
168 1
        return $this;
169
    }
170
171 1
    public function except(string ...$keys): DtoContract
172
    {
173 1
        foreach ($keys as $key) {
174 1
            if (array_key_exists($key, $this->with)) {
175
                unset($this->with[$key]);
176
            }
177 1
            $property = $this->properties[$key] ?? null;
178 1
            if (isset($property)) {
179 1
                $property->setVisible(false);
180
            }
181
        }
182
183 1
        return $this;
184
    }
185
186 2
    public function with(string $key, $value): DtoContract
187
    {
188 2
        if (array_key_exists($key, $this->properties)) {
189 1
            throw new PropertyAlreadyExistsException($key);
190
        }
191
192 1
        return $this->override($key, $value);
193
    }
194
195 4
    public function override(string $key, $value): DtoContract
196
    {
197 4
        if ($this->isImmutable()) {
198 1
            throw new ImmutableDtoException($key);
199
        }
200 3
        if (($propertyExists = array_key_exists($key, $this->properties) && $this->properties[$key]->isImmutable())) {
201 1
            throw new ImmutablePropertyDtoException($key);
202
        }
203
204 2
        if ($propertyExists) {
0 ignored issues
show
introduced by
The condition $propertyExists is always false.
Loading history...
205
            $property = $this->properties[$key];
206
            $property->set($value);
207
            if ($this->validate) {
208
                $this->validate();
209
            }
210
        } else {
211 2
            $this->with[$key] = $value;
212
        }
213
214 2
        return $this;
215
    }
216
217 17
    public function toArray(): array
218
    {
219 17
        $data = $this->all();
220 17
        $array = [];
221
222 17
        if (count($this->onlyKeys)) {
223 1
            $array = array_intersect_key($data, array_flip((array) $this->onlyKeys));
224
        } else {
225 16
            foreach ($data as $key => $propertyValue) {
226 15
                if (array_key_exists($key, $this->with) || (array_key_exists($key, $this->properties) && $this->properties[$key]->isVisible() && $this->properties[$key]->isInitialized())) {
227 15
                    $array[$key] = $propertyValue;
228
                }
229
            }
230
        }
231
232 17
        return $this->parseArray($array);
233
    }
234
235 17
    protected function parseArray(array $array): array
236
    {
237 17
        foreach ($array as $key => $value) {
238
            if (
239 14
                $value instanceof DataTransferObject
240 14
                || $value instanceof DataTransferObjectCollection
241
            ) {
242 3
                $array[$key] = $value->toArray();
243
244 3
                continue;
245
            }
246
247 14
            if (! is_array($value)) {
248 14
                continue;
249
            }
250
251 5
            $array[$key] = $this->parseArray($value);
252
        }
253
254 17
        return $array;
255
    }
256
257 30
    public function getValidationViolations()
258
    {
259 30
        $violations = [];
260 30
        foreach ($this->properties as $name => $property) {
261 28
            $value = $property->getValue();
262 28
            if ($value instanceof DataTransferObject) {
263 5
                $nestedViolations = $this->recursivelySortKeys($value->getValidationViolations(), $name);
264 5
                $violations = array_merge($violations, $nestedViolations);
265
            }
266 28
            if (is_iterable($value)) {
267 5
                foreach ($value as $key => $potentialDto) {
268 3
                    if ($potentialDto instanceof DataTransferObject) {
269 3
                        $nestedViolations = $this->recursivelySortKeys($potentialDto->getValidationViolations(), "$name.$key");
270 3
                        $violations = array_merge($violations, $nestedViolations);
271
                    }
272
                }
273
            }
274 28
            $violationList = $property->getViolations();
275 28
            if ($violationList === null || $violationList->count() <= 0) {
276 20
                continue;
277
            }
278 11
            $violations[$name] = $violationList;
279
        }
280
281 30
        return $violations;
282
    }
283
284 30
    public function validate()
285
    {
286 30
        $violations = $this->getValidationViolations();
287 30
        if (! empty($violations)) {
288 11
            throw new ValidatorException($violations);
289
        }
290
291 20
        return true;
292
    }
293
294 7
    protected function recursivelySortKeys(array $array, $str = '')
295
    {
296 7
        $sortedArray = [];
297 7
        foreach ($array as $key => $val) {
298 1
            if (is_array($val)) {
299
                if ($str == '') {
300
                    $this->recursivelySortKeys($val, $key);
301
                } else {
302
                    $this->recursivelySortKeys($val, $str.'.'.$key);
303
                }
304
            } else {
305 1
                if ($str == '') {
306
                    $sortedArray[$key] = $val;
307
                    echo $key."\n";
308
                } else {
309 1
                    $sortedArray[$str.'.'.$key] = $val;
310
                }
311
            }
312
        }
313
314 7
        return $sortedArray;
315
    }
316
}
317