Passed
Push — master ( 0c064e...87b116 )
by Arthur
08:29 queued 10s
created

DataTransferObject::setValidation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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