@@ -291,18 +291,95 @@ public static function renderAttribute(ReflectionAttribute $attribute): string
291291 if (count ($ attribute ->getArguments ()) > 0 ) {
292292 $ argumentsAsString = [];
293293 foreach ($ attribute ->getArguments () as $ argumentName => $ argumentValue ) {
294- $ renderedArgumentValue = var_export ($ argumentValue, true );
294+ $ argumentAsString = self :: renderAttributeArgumentValue ($ argumentValue );
295295 if (is_numeric ($ argumentName )) {
296- $ argumentsAsString [] = $ renderedArgumentValue ;
296+ $ argumentsAsString [] = $ argumentAsString ;
297297 } else {
298- $ argumentsAsString [] = "$ argumentName: $ renderedArgumentValue " ;
298+ $ argumentsAsString [] = "$ argumentName: $ argumentAsString " ;
299299 }
300300 }
301301 $ attributeAsString .= '( ' . implode (', ' , $ argumentsAsString ) . ') ' ;
302302 }
303303 return "#[ $ attributeAsString] " ;
304304 }
305305
306+ private static function renderAttributeInstantiation (ReflectionAttribute $ attribute ): string
307+ {
308+ $ attributeAsString = '\\' . $ attribute ->getName () . ': ' . get_debug_type ($ attribute ) ;
309+ if (count ($ attribute ->getArguments ()) > 0 ) {
310+ $ argumentsAsString = [];
311+ foreach ($ attribute ->getArguments () as $ argumentName => $ argumentValue ) {
312+ $ argumentAsString = self ::renderAttributeArgumentValue ($ argumentValue );
313+ if (is_numeric ($ argumentName )) {
314+ $ argumentsAsString [] = $ argumentAsString ;
315+ } else {
316+ $ argumentsAsString [] = "$ argumentName: $ argumentAsString " ;
317+ }
318+ }
319+ $ attributeAsString .= '( ' . implode (', ' , $ argumentsAsString ) . ') ' ;
320+ } else {
321+ $ attributeAsString .= '() ' ;
322+ }
323+ return "new $ attributeAsString " ;
324+ }
325+
326+ private static function renderAttributeArgumentValue (mixed $ attributeArgumentValue ): string
327+ {
328+ if (is_object ($ attributeArgumentValue )) {
329+ $ reflectionClass = new ReflectionClass ($ attributeArgumentValue );
330+ if ($ reflectionClass ->isEnum ()) {
331+ return '\\' . $ reflectionClass ->getName () . ':: ' . $ attributeArgumentValue ->name ;
332+ } else {
333+ $ fullyQualifiedName = '\\' . $ reflectionClass ->getName ();
334+ $ constructor = $ reflectionClass ->getConstructor ();
335+ if (!$ constructor ) {
336+ return "new {$ fullyQualifiedName }() " ;
337+ }
338+
339+ // Try to reconstruct named arguments by matching constructor parameter names
340+ // to public properties or getters ... this is obviously not perfect but probably
341+ // ok in most attributes
342+ $ constructorParameters = [];
343+ foreach ($ constructor ->getParameters () as $ parameter ) {
344+ $ parameterName = $ parameter ->getName ();
345+ $ parameterValue = null ;
346+ if ($ reflectionClass ->hasProperty ($ parameterName ) && $ reflectionClass ->getProperty ($ parameterName )->isPublic ()) {
347+ $ parameterValue = $ attributeArgumentValue ->$ parameterName ;
348+ } else {
349+ $ getterMethodName = 'get ' . ucfirst ($ parameterName );
350+ if ($ reflectionClass ->hasMethod ($ getterMethodName ) && $ reflectionClass ->getMethod ($ getterMethodName )->isPublic ()) {
351+ $ parameterValue = $ attributeArgumentValue ->$ getterMethodName ();
352+ }
353+ }
354+ if ($ parameter ->isDefaultValueAvailable ()) {
355+ $ parameterDefaultValue = $ parameter ->getDefaultValue ();
356+ if ($ parameterDefaultValue !== $ parameterValue ) {
357+ $ parameterValueAsString = self ::renderAttributeArgumentValue ($ parameterValue );
358+ $ constructorParameters [$ parameterName ] = "{$ parameterName }: {$ parameterValueAsString }" ;
359+ }
360+ } else {
361+ $ parameterValueAsString = self ::renderAttributeArgumentValue ($ parameterValue );
362+ $ constructorParameters [$ parameterName ] = "{$ parameterName }: {$ parameterValueAsString }" ;
363+ }
364+ }
365+
366+ return "new {$ fullyQualifiedName }( " . implode (', ' , $ constructorParameters ) . ") " ;
367+ }
368+ } elseif (is_array ($ attributeArgumentValue )) {
369+ $ argumentsAsString = [];
370+ foreach ($ attributeArgumentValue as $ key => $ value ) {
371+ $ argumentAsString = self ::renderAttributeArgumentValue ($ value );
372+ if (is_numeric ($ key )) {
373+ $ argumentsAsString [] = $ argumentAsString ;
374+ } else {
375+ $ argumentsAsString [] = "' $ key' => $ argumentAsString " ;
376+ }
377+ }
378+ return '[ ' . implode (', ' , $ argumentsAsString ) . '] ' ;
379+ }
380+ return var_export ($ attributeArgumentValue , true );
381+ }
382+
306383 /**
307384 * Render the source (string) form of an Annotation instance.
308385 *
0 commit comments