From f3d3b0e185353d7743531681260f7609a2da653d Mon Sep 17 00:00:00 2001 From: SDekkers Date: Sun, 31 Aug 2025 15:58:28 +0200 Subject: [PATCH] Refactor DTOs to use spatie/laravel-data and improve array handling Replaced spatie/data-transfer-object with spatie/laravel-data in BaseDTO and related classes. Refactored BaseDTO to remove inheritance from DataTransferObject, added custom property initialization, type casting, and improved array conversion methods. Updated BaseDTOCollection template and improved serialization logic in ExtractedEndpointData and Output/Parameter. Updated composer.json dependencies accordingly. --- camel/BaseDTO.php | 100 ++++++++++++++++++++- camel/BaseDTOCollection.php | 3 +- camel/Extraction/ExtractedEndpointData.php | 8 +- camel/Output/Parameter.php | 6 +- composer.json | 2 +- 5 files changed, 106 insertions(+), 13 deletions(-) diff --git a/camel/BaseDTO.php b/camel/BaseDTO.php index d568bd47..c77bb24c 100644 --- a/camel/BaseDTO.php +++ b/camel/BaseDTO.php @@ -3,10 +3,10 @@ namespace Knuckles\Camel; use Illuminate\Contracts\Support\Arrayable; -use Spatie\DataTransferObject\DataTransferObject; +use Spatie\LaravelData\Data; -class BaseDTO extends DataTransferObject implements Arrayable, \ArrayAccess +class BaseDTO implements Arrayable, \ArrayAccess { /** * @var array $custom @@ -14,6 +14,75 @@ class BaseDTO extends DataTransferObject implements Arrayable, \ArrayAccess */ public array $custom = []; + public function __construct(array $parameters = []) + { + // Initialize all properties to their default values first + $this->initializeProperties(); + + foreach ($parameters as $key => $value) { + if (property_exists($this, $key)) { + $this->$key = $this->castProperty($key, $value); + } + } + } + + protected function initializeProperties(): void + { + $reflection = new \ReflectionClass($this); + + foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + // Skip if already initialized (has a default value) + if ($property->hasDefaultValue()) { + continue; + } + + $type = $property->getType(); + if ($type && $type->allowsNull()) { + $this->$name = null; + } + } + } + + protected function castProperty(string $key, mixed $value): mixed + { + // If the value is already the correct type, return it as-is + if (!is_array($value)) { + return $value; + } + + // Get property type through reflection + $reflection = new \ReflectionClass($this); + if (!$reflection->hasProperty($key)) { + return $value; + } + + $property = $reflection->getProperty($key); + $type = $property->getType(); + + if ($type && $type instanceof \ReflectionNamedType && !$type->isBuiltin()) { + $className = $type->getName(); + + // If it's a DTO class in our namespace, instantiate it + if (class_exists($className) && is_subclass_of($className, self::class)) { + return new $className($value); + } + + // If it's another class in our namespace that has a constructor accepting arrays + if (class_exists($className)) { + try { + return new $className($value); + } catch (\Throwable $e) { + // If instantiation fails, return the original value + return $value; + } + } + } + + return $value; + } + public static function create(BaseDTO|array $data, BaseDTO|array $inheritFrom = []): static { if ($data instanceof static) { @@ -49,6 +118,15 @@ protected function parseArray(array $array): array return $array; } + public function toArray(): array + { + $array = []; + foreach (get_object_vars($this) as $property => $value) { + $array[$property] = $value; + } + return $this->parseArray($array); + } + public static function make(array|self $data): static { return $data instanceof static ? $data : new static($data); @@ -73,4 +151,22 @@ public function offsetUnset(mixed $offset): void { unset($this->$offset); } + + public function except(string ...$keys): array + { + $array = []; + foreach (get_object_vars($this) as $property => $value) { + if (!in_array($property, $keys)) { + $array[$property] = $value; + } + } + return $this->parseArray($array); + } + + public static function arrayOf(array $items): array + { + return array_map(function ($item) { + return $item instanceof static ? $item : new static($item); + }, $items); + } } diff --git a/camel/BaseDTOCollection.php b/camel/BaseDTOCollection.php index ba2d1762..f5a32ce5 100644 --- a/camel/BaseDTOCollection.php +++ b/camel/BaseDTOCollection.php @@ -4,10 +4,9 @@ use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Collection; -use Spatie\DataTransferObject\DataTransferObjectCollection; /** - * @template T of \Spatie\DataTransferObject\DataTransferObject + * @template T of \Knuckles\Camel\BaseDTO */ class BaseDTOCollection extends Collection { diff --git a/camel/Extraction/ExtractedEndpointData.php b/camel/Extraction/ExtractedEndpointData.php index ad95de24..f5983120 100644 --- a/camel/Extraction/ExtractedEndpointData.php +++ b/camel/Extraction/ExtractedEndpointData.php @@ -149,15 +149,17 @@ public function endpointId() */ public function forSerialisation() { - $copy = $this->except( + $copyArray = $this->except( // Get rid of all duplicate data 'cleanQueryParameters', 'cleanUrlParameters', 'fileParameters', 'cleanBodyParameters', // and objects used only in extraction 'route', 'controller', 'method', 'auth', ); // Remove these, since they're on the parent group object - $copy->metadata = $copy->metadata->except('groupName', 'groupDescription'); + if (isset($copyArray['metadata']) && $copyArray['metadata'] instanceof Metadata) { + $copyArray['metadata'] = $copyArray['metadata']->except('groupName', 'groupDescription'); + } - return $copy; + return $copyArray; } } diff --git a/camel/Output/Parameter.php b/camel/Output/Parameter.php index 75427457..2238ff0e 100644 --- a/camel/Output/Parameter.php +++ b/camel/Output/Parameter.php @@ -9,10 +9,6 @@ class Parameter extends \Knuckles\Camel\Extraction\Parameter public function toArray(): array { - if (empty($this->exceptKeys)) { - return $this->except('__fields')->toArray(); - } - - return parent::toArray(); + return $this->except('__fields'); } } diff --git a/composer.json b/composer.json index 98540824..4fe3dc61 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "ramsey/uuid": "^4.2.2", "shalvah/clara": "^3.1.0", "shalvah/upgrader": ">=0.6.0", - "spatie/data-transfer-object": "^2.6|^3.0", + "spatie/laravel-data": "^3.0|^4.0", "symfony/var-exporter": "^6.0|^7.0", "symfony/yaml": "^6.0|^7.0" },