Passed
Push — master ( 0efa8f...09a34a )
by Arthur
12:45
created

DataTransferObject::recursivelySortKeys()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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