33namespace Knuckles \Camel ;
44
55use Illuminate \Contracts \Support \Arrayable ;
6- use Spatie \DataTransferObject \DataTransferObject ;
76
87
9- class BaseDTO extends DataTransferObject implements Arrayable, \ArrayAccess
8+ class BaseDTO implements Arrayable, \ArrayAccess
109{
1110 /**
1211 * @var array $custom
1312 * Added so end-users can dynamically add additional properties for their own use.
1413 */
1514 public array $ custom = [];
1615
16+ public function __construct (array $ parameters = [])
17+ {
18+ // Initialize all properties to their default values first
19+ $ this ->initializeProperties ();
20+
21+ foreach ($ parameters as $ key => $ value ) {
22+ if (property_exists ($ this , $ key )) {
23+ $ this ->$ key = $ this ->castProperty ($ key , $ value );
24+ }
25+ }
26+ }
27+
28+ protected function initializeProperties (): void
29+ {
30+ $ reflection = new \ReflectionClass ($ this );
31+
32+ foreach ($ reflection ->getProperties (\ReflectionProperty::IS_PUBLIC ) as $ property ) {
33+ $ name = $ property ->getName ();
34+
35+ // Skip if already initialized (has a default value)
36+ if ($ property ->hasDefaultValue ()) {
37+ continue ;
38+ }
39+
40+ $ type = $ property ->getType ();
41+ if ($ type && $ type ->allowsNull ()) {
42+ $ this ->$ name = null ;
43+ }
44+ }
45+ }
46+
47+ protected function castProperty (string $ key , mixed $ value ): mixed
48+ {
49+ // If the value is already the correct type, return it as-is
50+ if (!is_array ($ value )) {
51+ return $ value ;
52+ }
53+
54+ // Get property type through reflection
55+ $ reflection = new \ReflectionClass ($ this );
56+ if (!$ reflection ->hasProperty ($ key )) {
57+ return $ value ;
58+ }
59+
60+ $ property = $ reflection ->getProperty ($ key );
61+ $ type = $ property ->getType ();
62+
63+ if ($ type && $ type instanceof \ReflectionNamedType && !$ type ->isBuiltin ()) {
64+ $ className = $ type ->getName ();
65+
66+ // If it's a DTO class in our namespace, instantiate it
67+ if (class_exists ($ className ) && is_subclass_of ($ className , self ::class)) {
68+ return new $ className ($ value );
69+ }
70+
71+ // If it's another class in our namespace that has a constructor accepting arrays
72+ if (class_exists ($ className )) {
73+ try {
74+ return new $ className ($ value );
75+ } catch (\Throwable $ e ) {
76+ // If instantiation fails, return the original value
77+ return $ value ;
78+ }
79+ }
80+ }
81+
82+ return $ value ;
83+ }
84+
1785 public static function create (BaseDTO |array $ data , BaseDTO |array $ inheritFrom = []): static
1886 {
1987 if ($ data instanceof static) {
@@ -49,6 +117,15 @@ protected function parseArray(array $array): array
49117 return $ array ;
50118 }
51119
120+ public function toArray (): array
121+ {
122+ $ array = [];
123+ foreach (get_object_vars ($ this ) as $ property => $ value ) {
124+ $ array [$ property ] = $ value ;
125+ }
126+ return $ this ->parseArray ($ array );
127+ }
128+
52129 public static function make (array |self $ data ): static
53130 {
54131 return $ data instanceof static ? $ data : new static ($ data );
@@ -73,4 +150,22 @@ public function offsetUnset(mixed $offset): void
73150 {
74151 unset($ this ->$ offset );
75152 }
153+
154+ public function except (string ...$ keys ): array
155+ {
156+ $ array = [];
157+ foreach (get_object_vars ($ this ) as $ property => $ value ) {
158+ if (!in_array ($ property , $ keys )) {
159+ $ array [$ property ] = $ value ;
160+ }
161+ }
162+ return $ this ->parseArray ($ array );
163+ }
164+
165+ public static function arrayOf (array $ items ): array
166+ {
167+ return array_map (function ($ item ) {
168+ return $ item instanceof static ? $ item : new static ($ item );
169+ }, $ items );
170+ }
76171}
0 commit comments