@@ -45,12 +45,9 @@ final class Entity
4545final class ArrayOf
4646{
4747 public bool $ error = false ;
48- public function __construct (
49- public string $ class = '' ,
50- ) {
51- if (trim ($ this ->class ) === '' ) {
52- $ this ->error = true ;
53- }
48+ public function __construct (string $ class = '' )
49+ {
50+ $ this ->error = empty (trim ($ class ));
5451 }
5552}
5653
@@ -67,7 +64,7 @@ public function __construct(array $data = [])
6764 /** @var \ReflectionNamedType|\ReflectionUnionType $type */
6865 $ type = $ property ->getType ();
6966 $ exists = array_key_exists ($ key , $ data );
70- $ isNull = !isset ($ data [$ key ]) ? true : $ data [$ key ] === null ;
67+ $ isNull = !isset ($ data [$ key ]) || $ data [$ key ] === null ;
7168 $ notExistsOrIsNull = !$ exists || $ isNull ;
7269 $ nullable = $ type ->allowsNull ();
7370 $ hasDefault = $ property ->hasDefaultValue ();
@@ -78,50 +75,52 @@ public function __construct(array $data = [])
7875 throw new Exception ('ArrayOf class 不能為空 ' );
7976 }
8077 $ arg = $ arrayOf [0 ]->getArguments ()[0 ];
81- if (!enum_exists ( $ arg ) && ! is_subclass_of ($ arg , self ::class)) {
78+ if (!is_subclass_of ($ arg , self ::class)) {
8279 throw new Exception ('ArrayOf 指定的 class 必須為 ImmutableBase 的子類 ' );
8380 }
8481 }
85- $ value = match (true ) {
86- $ notExistsOrIsNull && !$ nullable => throw new Exception ("必須傳入 $ type " ),
87- $ arrayOf && $ arg =>
88- match (true ) {
89- $ notExistsOrIsNull && $ nullable => null ,
90- $ notExistsOrIsNull && !$ nullable => throw new Exception ("必須傳入 array 或 array< {$ arg }> " ),
91- $ exists => is_array ($ data [$ key ]) ?
92- array_map (fn ($ item ) => match (true ) {
93- is_array ($ item ) => new $ arg ($ item ),
94- $ item instanceof $ arg => $ item ,
95- default => throw new Exception ("陣列內容必須是 $ arg 或符合其初始化所需之結構 " )
96- }, $ data [$ key ]) : throw new Exception ("必須傳入 array " ),
82+ $ this ->propertyInitialize (
83+ $ property ,
84+ match (true ) {
85+ $ notExistsOrIsNull => match (true ) {
86+ !$ nullable => throw new Exception ("必須傳入 $ type " ),
87+ $ nullable => $ hasDefault ? $ property ->getDefaultValue () : null ,
9788 },
98- $ notExistsOrIsNull && $ nullable && !$ hasDefault => null ,
99- $ notExistsOrIsNull && $ nullable && $ hasDefault => $ property ->getDefaultValue (),
100- $ exists => $ this ->valueDecide ($ type , $ data [$ key ]),
101- };
102- $ declaring = $ property ->getDeclaringClass ()->getName ();
103- if ($ declaring !== $ this ::class && $ property ->isReadOnly ()) {
104- if ($ property ->isInitialized ($ this )) {
105- return ;
89+ $ arrayOf && $ arg =>
90+ match (true ) {
91+ $ notExistsOrIsNull => throw new Exception ("必須傳入 array 或 array< {$ arg }> " ),
92+ is_array ($ data [$ key ]) =>
93+ array_map (fn ($ item ) => match (true ) {
94+ is_array ($ item ) => new $ arg ($ item ),
95+ $ item instanceof $ arg => $ item ,
96+ default => throw new Exception ("陣列內容必須是 $ arg 或符合其初始化所需之結構 " )
97+ }, $ data [$ key ]),
98+ default => throw new Exception ("必須傳入 array " ),
99+ },
100+ $ exists => $ this ->valueDecide ($ type , $ data [$ key ]),
106101 }
107- $ assign = self ::$ classBoundSetter [$ declaring ] ??= Closure::bind (
108- function (object $ obj , string $ prop , mixed $ val ): void {
109- $ obj ->$ prop = $ val ;
110- },
111- null ,
112- $ declaring
113- );
114- $ assign ($ this , $ property ->getName (), $ value );
115- } else {
116- $ property ->setValue ($ this , $ value );
117- }
102+ );
118103 } catch (Exception $ e ) {
119- if ($ msg = $ e ->getMessage ()) {
120- throw new Exception ("$ key $ msg " );
121- }
104+ throw new Exception ("$ key {$ e ->getMessage ()}" );
122105 }
123106 });
124107 }
108+ private function propertyInitialize (\ReflectionProperty $ property , mixed $ value ): void
109+ {
110+ $ declaring = $ property ->getDeclaringClass ()->getName ();
111+ if ($ declaring !== $ this ::class && $ property ->isReadOnly ()) {
112+ if ($ property ->isInitialized ($ this )) {
113+ return ;
114+ }
115+ (self ::$ classBoundSetter [$ declaring ] ??= Closure::bind (
116+ fn (object $ obj , string $ prop , mixed $ val ) => $ obj ->$ prop = $ val ,
117+ null ,
118+ $ declaring
119+ ))($ this , $ property ->getName (), $ value );
120+ } else {
121+ $ property ->setValue ($ this , $ value );
122+ }
123+ }
125124 private static function getReflection (object $ obj ): ReflectionClass
126125 {
127126 return self ::$ reflectionsCache [static ::class] ??= new ReflectionClass ($ obj );
@@ -135,7 +134,7 @@ private function walkProperties(callable $callback): void
135134 {
136135 $ ref = self ::getReflection ($ this );
137136 $ attrs = array_map (fn ($ attr ) => $ attr ->getName (), $ ref ->getAttributes ());
138- $ set = array_fill_keys ($ attrs, true );
137+ $ set = array_flip ($ attrs );
139138 $ mode = match (true ) {
140139 isset ($ set [DataTransferObject::class]) => 1 ,
141140 isset ($ set [ValueObject::class]) => 2 ,
@@ -148,21 +147,18 @@ private function walkProperties(callable $callback): void
148147 }
149148 $ chain = array_reverse ($ chain );
150149 foreach ($ chain as $ cls ) {
151- foreach ($ cls ->getProperties () as $ p ) {
150+ $ properties = $ cls ->getProperties ();
151+ array_filter ($ properties , fn ($ p ) => $ p ->getName () === $ cls ->getName () || $ cls ->getName () !== self ::class);
152+ foreach ($ properties as $ p ) {
152153 $ isPublic = $ p ->isPublic ();
153154 $ propertyName = $ p ->getName ();
154155 $ className = $ p ->getDeclaringClass ()->getName ();
155- if ($ className !== $ cls ->getName () || $ className === self ::class) {
156- continue ;
157- }
158- if ($ mode === 2 || $ mode === 3 ) {
159- if ($ isPublic ) {
160- throw new Exception ("$ className $ propertyName 不允許為 public " );
161- }
162- } else {
156+ if ($ mode === 1 ) {
163157 if (!$ isPublic || !$ p ->isReadOnly ()) {
164158 throw new Exception ("$ className $ propertyName 必須為 public 且 readonly " );
165159 }
160+ } elseif ($ isPublic ) {
161+ throw new Exception ("$ className $ propertyName 不允許為 public " );
166162 }
167163 $ callback ($ p );
168164 }
@@ -182,7 +178,7 @@ final public function with(array $data): static
182178 try {
183179 $ name = $ property ->getName ();
184180 $ type = $ property ->getType ();
185- $ newData [$ name ] = in_array ($ name , array_keys ( $ data) ) ?
181+ $ newData [$ name ] = array_key_exists ($ name , $ data ) ?
186182 $ this ->valueDecide ($ type , $ data [$ name ]) :
187183 $ property ->getValue ($ this );
188184 } catch (Exception $ e ) {
@@ -208,57 +204,73 @@ final public function toArray(): array
208204 }
209205 private function toArrayOrValue (mixed $ value )
210206 {
211- return is_object ($ value ) && method_exists ($ value , 'toArray ' ) ? $ value ->toArray () : $ value ;
207+ if (is_object ($ value )) {
208+ if (method_exists ($ value , 'toArray ' )) {
209+ return $ value ->toArray ();
210+ }
211+ }
212+ return $ value ;
212213 }
213214 private function valueDecide (ReflectionNamedType |ReflectionUnionType $ type , mixed $ value ): mixed
214215 {
215216 if ($ type instanceof ReflectionUnionType) {
216- $ names = array_map (fn ($ e ) => $ e ->getName (), $ type ->getTypes ());
217- if (!in_array ('array ' , $ names , true ) && is_array ($ value )) {
218- throw new Exception ('型別為複合且不包含array,須傳入已實例化的物件。 ' );
219- }
220- foreach ($ type ->getTypes () as $ t ) {
221- try {
222- return $ this ->valueDecide ($ t , $ value );
223- } catch (Exception ) {
224- }
225- }
226- $ excepts = implode ('| ' , $ names );
227- $ valueType = is_object ($ value ) ? get_class ($ value ) : gettype ($ value );
228- throw new Exception ("型別錯誤,期望: {$ excepts },傳入: {$ valueType }。 " );
217+ return $ this ->unionTypeDecide ($ type , $ value );
229218 } else {
230219 if (!$ type ->isBuiltin ()) {
231- $ class = $ type ->getName ();
232- $ value = match (true ) {
233- is_array ($ value ) && is_subclass_of ($ class , self ::class) => new $ class ($ value ),
234- is_object ($ value ) => $ value ,
235- $ type ->allowsNull () && $ value === null => null ,
236- is_string ($ value ) && enum_exists ($ class ) => (function () use ($ class , $ value ) {
237- try {
238- return $ class ::tryFrom ($ value ) ?? constant ("$ class:: $ value " );
239- } catch (Throwable ) {
240- throw new Exception ("$ value 不是 $ class 的期望值 " );
241- }
242- })(),
243- default => throw new Exception ("型別錯誤,期望: {$ class },傳入: " . (is_object ($ value ) ? get_class ($ value ) : gettype ($ value )))
244- };
245- } elseif ($ this ->builtinTypeValidate ($ value , $ type ->getName ()) === false ) {
246- if ($ type ->allowsNull () && $ value === null ) {
247- return null ;
248- } else {
249- throw new Exception ("型別錯誤,期望: {$ type ->getName ()},傳入: " .(is_object ($ value ) ? get_class ($ value ) : gettype ($ value )));
250- }
220+ return $ this ->namedTypeDecide ($ type , $ value );
221+ } elseif (
222+ $ this ->builtinTypeValidate ($ value , $ type ->getName ()) === false &&
223+ !$ this ->validNullValue ($ type , $ value )
224+ ) {
225+ throw new Exception ("型別錯誤,期望: {$ type ->getName ()},傳入: " .(is_object ($ value ) ? get_class ($ value ) : gettype ($ value )));
251226 }
252227 }
253228 return $ value ;
254229 }
230+ private function unionTypeDecide (ReflectionUnionType $ type , mixed $ value )
231+ {
232+ $ names = array_map (fn ($ e ) => $ e ->getName (), $ type ->getTypes ());
233+ if (!in_array ('array ' , $ names , true ) && is_array ($ value )) {
234+ throw new Exception ('型別為複合且不包含array,須傳入已實例化的物件。 ' );
235+ }
236+ foreach ($ type ->getTypes () as $ t ) {
237+ try {
238+ return $ this ->valueDecide ($ t , $ value );
239+ } catch (Exception ) {
240+ }
241+ }
242+ $ excepts = implode ('| ' , $ names );
243+ $ valueType = is_object ($ value ) ? get_class ($ value ) : gettype ($ value );
244+ throw new Exception ("型別錯誤,期望: {$ excepts },傳入: {$ valueType }。 " );
245+ }
246+ private function namedTypeDecide (ReflectionNamedType $ type , mixed $ value )
247+ {
248+ $ class = $ type ->getName ();
249+ return match (true ) {
250+ is_array ($ value ) && is_subclass_of ($ class , self ::class) => new $ class ($ value ),
251+ is_object ($ value ) => $ value ,
252+ $ this ->validNullValue ($ type , $ value ) => null ,
253+ is_string ($ value ) && enum_exists ($ class ) => (function () use ($ class , $ value ) {
254+ try {
255+ return $ class ::tryFrom ($ value ) ?? constant ("$ class:: $ value " );
256+ } catch (Throwable ) {
257+ throw new Exception ("$ value 不是 $ class 的期望值 " );
258+ }
259+ })(),
260+ default => throw new Exception ("型別錯誤,期望: {$ class },傳入: " . (is_object ($ value ) ? get_class ($ value ) : gettype ($ value )))
261+ };
262+ }
263+ private function validNullValue (ReflectionNamedType $ type , $ value )
264+ {
265+ return $ type ->allowsNull () && $ value === null ;
266+ }
255267 private function builtinTypeValidate (mixed $ value , string $ type ): bool
256268 {
257269 return match ($ type ) {
258- 'int ' , ' integer ' => is_int ($ value ),
259- 'float ' , ' double ' => is_float ($ value ),
270+ 'int ' => is_int ($ value ),
271+ 'float ' => is_float ($ value ),
260272 'string ' => is_string ($ value ),
261- 'bool ' , ' boolean ' => is_bool ($ value ),
273+ 'bool ' => is_bool ($ value ),
262274 'array ' => is_array ($ value ),
263275 'object ' => is_object ($ value ),
264276 'null ' => $ value === null ,
0 commit comments