Passed
Push — master ( 0756fe...706dc7 )
by Arthur
14:47
created

DataTransferObject::getPublicProperties()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 10
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A DataTransferObject::isImmutable() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Larapie\DataTransferObject;
6
7
use Larapie\DataTransferObject\Contracts\AutomaticValidation;
8
use Larapie\DataTransferObject\Exceptions\ValidatorException;
9
use Larapie\DataTransferObject\Factories\PropertyFactory;
10
use Larapie\DataTransferObject\Violations\PropertyRequiredViolation;
11
use Larapie\DataTransferObject\Contracts\DtoContract;
12
use Larapie\DataTransferObject\Exceptions\ImmutableDtoException;
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
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
36
    public function __construct(array $parameters)
37
    {
38
        $this->boot($parameters);
39
    }
40
41
    /**
42
     * Boot the dto and process all parameters.
43
     * @param array $parameters
44
     * @throws \ReflectionException
45
     */
46
    protected function boot(array $parameters): void
47
    {
48
        $this->properties = (new PropertyFactory($this))->build($parameters);
49
        $this->determineImmutability();
50
        if($this instanceof AutomaticValidation)
51
            $this->validate();
52
    }
53
54
    protected function determineImmutability()
55
    {
56
        /* If the dto itself is not immutable but some properties are chain them immutable  */
57
        foreach ($this->properties as $property) {
58
            if ($property->isImmutable()) {
59
                $this->chainPropertyImmutable($property);
60
            }
61
        }
62
    }
63
64
    protected function setImmutable(): void
65
    {
66
        if (!$this->isImmutable()) {
67
            $this->immutable = true;
68
            foreach ($this->properties as $property) {
69
                $this->chainPropertyImmutable($property);
70
            }
71
        }
72
    }
73
74
    protected function chainPropertyImmutable(Property $property)
75
    {
76
        $dto = $property->getValue();
77
        if ($dto instanceof DataTransferObject) {
78
            $dto->setImmutable();
79
        } elseif (is_iterable($dto)) {
80
            foreach ($dto as $aPotentialDto) {
81
                if ($aPotentialDto instanceof DataTransferObject) {
82
                    $aPotentialDto->setImmutable();
83
                }
84
            }
85
        }
86
    }
87
88
89
    /**
90
     * Immutable behavior
91
     * Throw error if a user tries to set a property.
92
     * @param $name
93
     * @param $value
94
     * @throws ImmutableDtoException|ImmutablePropertyDtoException|PropertyNotFoundDtoException
95
     */
96
    public function __set($name, $value)
97
    {
98
        if ($this->immutable) {
99
            throw new ImmutableDtoException($name);
100
        }
101
        if (!isset($this->properties[$name])) {
102
            throw new PropertyNotFoundDtoException($name, get_class($this));
103
        }
104
105
        if ($this->properties[$name]->isImmutable()) {
106
            throw new ImmutablePropertyDtoException($name);
107
        }
108
        $this->$name = $value;
109
    }
110
111
    public function &__get($name)
112
    {
113
        return $this->properties[$name]->value;
114
    }
115
116
    public function isImmutable(): bool
117
    {
118
        return $this->immutable;
119
    }
120
121
    public function all(): array
122
    {
123
        $data = [];
124
125
        foreach ($this->properties as $property) {
126
            $value = $property->getValue();
127
            $data[$property->getName()] = $value instanceof DtoContract ? $value->toArray() : $value;
128
        }
129
130
        return array_merge($data, $this->with);
131
    }
132
133
    public function only(string ...$keys): DtoContract
134
    {
135
        $this->onlyKeys = array_merge($this->onlyKeys, $keys);
136
137
        return $this;
138
    }
139
140
    public function except(string ...$keys): DtoContract
141
    {
142
        foreach ($keys as $key) {
143
            if (array_key_exists($key, $this->with)) {
144
                unset($this->with[$key]);
145
            }
146
            $property = $this->properties[$key] ?? null;
147
            if (isset($property)) {
148
                $property->setVisible(false);
149
            }
150
        }
151
        return $this;
152
    }
153
154
    public function with(string $key, $value): DtoContract
155
    {
156
        if (array_key_exists($key, $this->properties)) {
157
            throw new PropertyAlreadyExistsException($key);
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
        } else {
177
            $this->with[$key] = $value;
178
        }
179
180
        return $this;
181
    }
182
183
    public function toArray(): array
184
    {
185
        $data = $this->all();
186
        $array = [];
187
188
        if (count($this->onlyKeys)) {
189
            $array = array_intersect_key($data, array_flip((array)$this->onlyKeys));
190
        } else {
191
            foreach ($data as $key => $propertyValue) {
192
                if (array_key_exists($key, $this->properties) && $this->properties[$key]->isVisible() && $this->properties[$key]->isInitialized()) {
193
                    $array[$key] = $propertyValue;
194
                }
195
            }
196
        }
197
198
        return $this->parseArray($array);
199
    }
200
201
    protected function parseArray(array $array): array
202
    {
203
        foreach ($array as $key => $value) {
204
            if (
205
                $value instanceof DataTransferObject
206
                || $value instanceof DataTransferObjectCollection
207
            ) {
208
                $array[$key] = $value->toArray();
209
210
                continue;
211
            }
212
213
            if (!is_array($value)) {
214
                continue;
215
            }
216
217
            $array[$key] = $this->parseArray($value);
218
        }
219
220
        return $array;
221
    }
222
223
    public function throwValidationException()
224
    {
225
        $violations = [];
226
        foreach ($this->properties as $propertyName => $property) {
227
            $violationList = $property->getViolations();
228
229
            if ($violationList === null || $violationList->count() <= 0)
230
                continue;
231
            foreach ($violationList as $violation) {
232
                if ($violation instanceof PropertyRequiredViolation) {
233
                    $violations[$propertyName] = [$violation];
234
                    break;
235
                }
236
                $violations[$propertyName][] = $violation;
237
            }
238
        }
239
        throw new ValidatorException($violations);
240
    }
241
242
    public function validate()
243
    {
244
        $violations = [];
245
        foreach ($this->properties as $name => $property) {
246
            $violationList = $property->getViolations();
247
            if ($violationList === null || $violationList->count() <= 0)
248
                continue;
249
            $violations[$name] = $violationList;
250
        }
251
252
        if (!empty($violations))
253
            throw new ValidatorException($violations);
254
255
    }
256
}
257