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" },