99use Throwable ;
1010use ReflectionClass ;
1111use ReflectionNamedType ;
12+ use ReflectionProperty ;
1213use ReflectionUnionType ;
1314
1415/**
@@ -53,11 +54,14 @@ public function __construct(string $class = '')
5354
5455abstract class ImmutableBase
5556{
57+ private int $ mode ;
58+ private ReflectionClass $ ref ;
5659 /** @var ReflectionClass[] $reflectionsCache */
5760 private static array $ reflectionsCache = [];
5861 private static array $ classBoundSetter = [];
5962 public function __construct (array $ data = [])
6063 {
64+ $ this ->constructInitialize ();
6165 $ this ->walkProperties (function (\ReflectionProperty $ property ) use ($ data ) {
6266 try {
6367 $ key = $ property ->name ;
@@ -105,9 +109,22 @@ public function __construct(array $data = [])
105109 }
106110 });
107111 }
112+ private function constructInitialize ()
113+ {
114+ $ this ->ref ??= self ::getReflection ($ this );
115+ foreach ($ this ->ref ->getAttributes () as $ attr ) {
116+ $ set [$ attr ->name ] = true ;
117+ }
118+ $ this ->mode ??= match (true ) {
119+ isset ($ set [DataTransferObject::class]) => 1 ,
120+ isset ($ set [ValueObject::class]) => 2 ,
121+ isset ($ set [Entity::class]) => 3 ,
122+ default => throw new Exception ('ImmutableBase 子類必須使用 DataTransferObject、ValueObject 或 Entity 任一標註 ' ),
123+ };
124+ }
108125 private function propertyInitialize (\ReflectionProperty $ property , mixed $ value ): void
109126 {
110- $ declaring = $ property ->getDeclaringClass ()-> getName () ;
127+ $ declaring = $ property ->class ;
111128 if ($ declaring !== $ this ::class && $ property ->isReadOnly ()) {
112129 if ($ property ->isInitialized ($ this )) {
113130 return ;
@@ -116,7 +133,7 @@ private function propertyInitialize(\ReflectionProperty $property, mixed $value)
116133 fn (object $ obj , string $ prop , mixed $ val ) => $ obj ->$ prop = $ val ,
117134 null ,
118135 $ declaring
119- ))($ this , $ property ->getName () , $ value );
136+ ))($ this , $ property ->name , $ value );
120137 } else {
121138 $ property ->setValue ($ this , $ value );
122139 }
@@ -132,36 +149,23 @@ private static function getReflection(object $obj): ReflectionClass
132149 */
133150 private function walkProperties (callable $ callback ): void
134151 {
135- $ ref = self ::getReflection ($ this );
136- $ attrs = array_map (fn ($ attr ) => $ attr ->getName (), $ ref ->getAttributes ());
137- $ set = array_flip ($ attrs );
138- $ mode = match (true ) {
139- isset ($ set [DataTransferObject::class]) => 1 ,
140- isset ($ set [ValueObject::class]) => 2 ,
141- isset ($ set [Entity::class]) => 3 ,
142- default => throw new Exception ('ImmutableBase 子類必須使用 DataTransferObject、ValueObject 或 Entity 任一標註 ' ),
143- };
144- $ chain = [];
145- for ($ c = $ ref ; $ c && $ c ->getName () !== self ::class; $ c = $ c ->getParentClass ()) {
146- $ chain [] = $ c ;
152+ $ properties = [];
153+ for ($ c = $ this ->ref ; $ c && $ c ->name !== self ::class; $ c = $ c ->getParentClass ()) {
154+ array_unshift ($ properties , ...$ c ->getProperties ());
147155 }
148- $ chain = array_reverse ($ chain );
149- foreach ($ chain as $ cls ) {
150- $ properties = $ cls ->getProperties ();
151- array_filter ($ properties , fn ($ p ) => $ p ->getName () === $ cls ->getName () || $ cls ->getName () !== self ::class);
152- foreach ($ properties as $ p ) {
153- $ isPublic = $ p ->isPublic ();
154- $ propertyName = $ p ->getName ();
155- $ className = $ p ->getDeclaringClass ()->getName ();
156- if ($ mode === 1 ) {
157- if (!$ isPublic || !$ p ->isReadOnly ()) {
158- throw new Exception ("$ className $ propertyName 必須為 public 且 readonly " );
159- }
160- } elseif ($ isPublic ) {
161- throw new Exception ("$ className $ propertyName 不允許為 public " );
156+ foreach ($ properties as $ p ) {
157+ /** @var ReflectionProperty $p */
158+ $ isPublic = $ p ->isPublic ();
159+ $ propertyName = $ p ->name ;
160+ $ className = $ p ->class ;
161+ if ($ this ->mode === 1 ) {
162+ if (!$ isPublic || !$ p ->isReadOnly ()) {
163+ throw new Exception ("$ className $ propertyName 必須為 public 且 readonly " );
162164 }
163- $ callback ($ p );
165+ } elseif ($ isPublic ) {
166+ throw new Exception ("$ className $ propertyName 不允許為 public " );
164167 }
168+ $ callback ($ p );
165169 }
166170 }
167171
@@ -176,7 +180,7 @@ final public function with(array $data): static
176180 $ ref = self ::getReflection ($ this );
177181 foreach ($ ref ->getProperties () as $ property ) {
178182 try {
179- $ name = $ property ->getName () ;
183+ $ name = $ property ->name ;
180184 $ type = $ property ->getType ();
181185 $ newData [$ name ] = array_key_exists ($ name , $ data ) ?
182186 $ this ->valueDecide ($ type , $ data [$ name ]) :
@@ -196,7 +200,7 @@ final public function toArray(): array
196200 {
197201 $ properties = [];
198202 $ this ->walkProperties (function (\ReflectionProperty $ property ) use (&$ properties ) {
199- $ properties [$ property ->getName () ] = is_array ($ value = $ property ->getValue ($ this )) ?
203+ $ properties [$ property ->name ] = is_array ($ value = $ property ->getValue ($ this )) ?
200204 array_map ([$ this , 'toArrayOrValue ' ], $ value ) :
201205 $ this ->toArrayOrValue ($ value );
202206 });
@@ -222,25 +226,26 @@ private function valueDecide(ReflectionNamedType|ReflectionUnionType $type, mixe
222226 $ this ->builtinTypeValidate ($ value , $ type ->getName ()) === false &&
223227 !$ this ->validNullValue ($ type , $ value )
224228 ) {
225- throw new Exception ("型別錯誤,期望: {$ type ->getName ()},傳入: " .(is_object ($ value ) ? get_class ( $ value) : gettype ($ value )));
229+ throw new Exception ("型別錯誤,期望: {$ type ->getName ()},傳入: " .(is_object ($ value ) ? $ value::class : gettype ($ value )));
226230 }
227231 }
228232 return $ value ;
229233 }
230234 private function unionTypeDecide (ReflectionUnionType $ type , mixed $ value )
231235 {
232- $ names = array_map (fn ($ e ) => $ e ->getName (), $ type ->getTypes ());
236+ $ types = $ type ->getTypes ();
237+ $ names = array_map (fn ($ e ) => $ e ->getName (), $ types );
233238 if (!in_array ('array ' , $ names , true ) && is_array ($ value )) {
234239 throw new Exception ('型別為複合且不包含array,須傳入已實例化的物件。 ' );
235240 }
236- foreach ($ type -> getTypes () as $ t ) {
241+ foreach ($ types as $ t ) {
237242 try {
238243 return $ this ->valueDecide ($ t , $ value );
239244 } catch (Exception ) {
240245 }
241246 }
242247 $ excepts = implode ('| ' , $ names );
243- $ valueType = is_object ($ value ) ? get_class ( $ value) : gettype ($ value );
248+ $ valueType = is_object ($ value ) ? $ value::class : gettype ($ value );
244249 throw new Exception ("型別錯誤,期望: {$ excepts },傳入: {$ valueType }。 " );
245250 }
246251 private function namedTypeDecide (ReflectionNamedType $ type , mixed $ value )
@@ -257,7 +262,7 @@ private function namedTypeDecide(ReflectionNamedType $type, mixed $value)
257262 throw new Exception ("$ value 不是 $ class 的期望值 " );
258263 }
259264 })(),
260- default => throw new Exception ("型別錯誤,期望: {$ class },傳入: " . (is_object ($ value ) ? get_class ( $ value) : gettype ($ value )))
265+ default => throw new Exception ("型別錯誤,期望: {$ class },傳入: " . (is_object ($ value ) ? $ value::class : gettype ($ value )))
261266 };
262267 }
263268 private function validNullValue (ReflectionNamedType $ type , $ value )
0 commit comments