Skip to content

Commit 2fa22fe

Browse files
committed
feat: mise en place d'une classe de gestion des DTO
1 parent 62c059b commit 2fa22fe

1 file changed

Lines changed: 294 additions & 0 deletions

File tree

Data/DataTransfertObject.php

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Blitz PHP framework.
5+
*
6+
* (c) 2022 Dimitri Sitchet Tomkeu <devcode.dst@gmail.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace BlitzPHP\Utilities\Data;
13+
14+
use BlitzPHP\Annotations\AnnotationReader;
15+
use BlitzPHP\Contracts\Support\Arrayable;
16+
use BlitzPHP\Contracts\Support\Jsonable;
17+
use BlitzPHP\Utilities\Date;
18+
use BlitzPHP\Utilities\Helpers;
19+
use BlitzPHP\Utilities\Iterable\Arr;
20+
use BlitzPHP\Utilities\Iterable\Collection;
21+
use InvalidArgumentException;
22+
use JsonSerializable;
23+
use ReflectionClass;
24+
use ReflectionProperty;
25+
use Stringable;
26+
27+
class DataTransfertObject implements Arrayable, Jsonable
28+
{
29+
/**
30+
* Donnees originales sans alteration quelconque
31+
*/
32+
private array $original = [];
33+
34+
/**
35+
* Proprietes a ne pas afficher lors de la serialisation
36+
*/
37+
protected array $except = [];
38+
39+
/**
40+
* Proprietes a afficher lors de la serialisation
41+
*/
42+
protected array $only = [];
43+
44+
public function __construct(protected array $attributes = [])
45+
{
46+
foreach ($attributes as $key => $value) {
47+
$this->parseProperty($key, $value);
48+
}
49+
}
50+
51+
/**
52+
* Restreindre les donnees serialisable du DTO en un sous-ensemble specifique
53+
*/
54+
public function only(string ...$keys): static
55+
{
56+
$dto = clone $this;
57+
58+
$dto->only = [...$this->only, ...$keys];
59+
60+
return $dto;
61+
}
62+
63+
/**
64+
* Exclure un sous-ensemble specifique des donnees serialisable du DTO
65+
*/
66+
public function except(string ...$keys): static
67+
{
68+
$dto = clone $this;
69+
70+
$dto->except = [...$this->except, ...$keys];
71+
72+
return $dto;
73+
}
74+
75+
/**
76+
* Cloner les donnees du DTO dans un nouveau DTO tout en ajoutant des elements
77+
*/
78+
public function clone(...$args): static
79+
{
80+
return static::make(array_merge($this->toArray(), $args));
81+
}
82+
83+
/**
84+
* Cree un tableau de DTO a partir d'un tableau d'element
85+
*
86+
* @param array<mixed>[] $arrayOfattributes Tableau a 2 dimensions des elements a transformer en DTO
87+
*
88+
* @return static[]
89+
*/
90+
public static function arrayOf(array $arrayOfattributes): array
91+
{
92+
return static::collection($arrayOfattributes)->all();
93+
}
94+
95+
/**
96+
* Cree une collection de DTO a partir d'un tableau d'element
97+
*
98+
* @param array<mixed>[] $arrayOfattributes Tableau a 2 dimensions des elements a transformer en DTO
99+
*
100+
* @return Collection<static>
101+
*/
102+
public static function collection(array $arrayOfattributes): Collection
103+
{
104+
if (Arr::dimensions($arrayOfattributes) < 2) {
105+
throw new InvalidArgumentException();
106+
}
107+
108+
return Helpers::collect($arrayOfattributes)->map(static fn (array $attributes) => static::make($attributes));
109+
}
110+
111+
/**
112+
* Cree une instance du DTO
113+
*/
114+
public static function make(array $attributes): static
115+
{
116+
return new static($attributes);
117+
}
118+
119+
/**
120+
* Recupere toutes les donnees du DTO
121+
*/
122+
public function all(): array
123+
{
124+
$data = $this->attributes;
125+
126+
foreach ($this->getProperties() as $property) {
127+
if ($property->isStatic()) {
128+
continue;
129+
}
130+
131+
$property->setAccessible(true);
132+
if (! $property->isInitialized($this)) {
133+
continue;
134+
}
135+
136+
$data[$property->getName()] = $property->getValue($this);
137+
}
138+
139+
return $data;
140+
}
141+
142+
/**
143+
* Converti l'instance en tableau pour la serialisation
144+
*/
145+
public function toArray(): array
146+
{
147+
if (count($this->only)) {
148+
$data = Arr::only($this->all(), $this->only);
149+
} else {
150+
$data = Arr::except($this->all(), $this->except);
151+
}
152+
153+
$data = Arr::except($data, array_keys($this->attributes));
154+
155+
foreach ($data as $key => $value) {
156+
$data[$key] = $this->format($value);
157+
}
158+
159+
return $data;
160+
}
161+
162+
/**
163+
* Converti l'instance en sa representation json
164+
*/
165+
public function toJson(int $options = 0): string
166+
{
167+
return json_encode($this->toArray(), $options);
168+
}
169+
170+
public function __get($name)
171+
{
172+
return $this->attributes[$name] ?? null;
173+
}
174+
175+
public function __set($name, $value)
176+
{
177+
$this->attributes[$name] = $value;
178+
}
179+
180+
/**
181+
* Cast une valeur en fonction du type desiré par le developpeur
182+
*/
183+
protected function cast(mixed $value, string $type): mixed
184+
{
185+
return match ($type) {
186+
'int', 'integer' => (int) $value,
187+
'string' => (string) $value,
188+
'float', 'number', 'double' => (float) $value,
189+
'object' => (object) $value ,
190+
'bool' , 'boolean' => (bool) $value ,
191+
default => class_exists($type) ? new $type($value) : $value,
192+
};
193+
}
194+
195+
/**
196+
* Format les valeurs pour la serialisation
197+
*/
198+
protected function format(mixed $value): mixed
199+
{
200+
if (is_array($value)) {
201+
$value = Helpers::collect($value);
202+
}
203+
204+
if ($value instanceof Collection) {
205+
return $value->map(fn ($v) => $this->format($v))->all();
206+
}
207+
208+
if ($value instanceof Date) {
209+
return $value->format('Y-m-d H:i:s');
210+
}
211+
if ($value instanceof Arrayable) {
212+
return $value->toArray();
213+
}
214+
if ($value instanceof Jsonable) {
215+
return $value->toJson();
216+
}
217+
if ($value instanceof Stringable) {
218+
return $value->__toString();
219+
}
220+
if ($value instanceof JsonSerializable) {
221+
return $value->jsonSerialize();
222+
}
223+
224+
return $value;
225+
}
226+
227+
/**
228+
* Renvoi toutes les proprietes publique de l'instance en cours de traitement
229+
*
230+
* @return ReflectionProperty[]
231+
*/
232+
protected function getProperties(): array
233+
{
234+
return (new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC);
235+
}
236+
237+
/**
238+
* Verifie si une cle existe comme etant propriete de la classe et le caste au bon type le cas echeant
239+
*/
240+
private function parseProperty(string $key, mixed $value): void
241+
{
242+
$this->original[$key] = $value;
243+
244+
if (! property_exists($this, $key)) {
245+
return;
246+
}
247+
248+
unset($this->attributes[$key]);
249+
250+
$property = new ReflectionProperty($this, $key);
251+
$type = $property->getType()?->getName();
252+
$annotations = AnnotationReader::formProperty($property, $key, '@var');
253+
254+
if (empty($value) && $property->hasDefaultValue()) {
255+
$value = $property->getDefaultValue();
256+
}
257+
258+
if (in_array($type, [null, 'null', 'mixed'], true) && [] === $annotations) {
259+
$this->{$key} = $value;
260+
261+
return;
262+
}
263+
264+
$subtype = $type;
265+
$annotationType = isset($annotations[0]) ? $annotations[0]->type : 'mixed';
266+
267+
if (null !== $type && is_a($type, Collection::class, true)) {
268+
$subtype = 'collection';
269+
}
270+
271+
if (str_ends_with($annotationType, '[]')) {
272+
$annotationType = str_replace('[]', '', $annotationType);
273+
274+
if (in_array($type, ['null', null, 'mixed'], true)) {
275+
$subtype = 'collection';
276+
} else {
277+
$subtype = 'array';
278+
$type = $annotationType;
279+
}
280+
}
281+
282+
if (in_array($type, [null, 'null', 'mixed'], true)) {
283+
$type = $annotationType;
284+
}
285+
286+
$result = Helpers::collect($value)->map(fn ($v) => $this->cast($v, $type));
287+
288+
$this->{$key} = match ($subtype) {
289+
'collection' => $result,
290+
'array' => $result->all(),
291+
default => $result->first()
292+
};
293+
}
294+
}

0 commit comments

Comments
 (0)