Skip to content

Commit a37bea6

Browse files
committed
BUGFIX: Render nested attributes and handle Enum and Object arguments
The code generated by var_export for objects (and enums) is not valid to be used in attributes as those require a constant expression. This change addresses that by: - rendering Enums correctly - rendering other objects by filling constructor arguments with public property or getter values - handling array arguments by calling the above recursively
1 parent 8cde9d2 commit a37bea6

File tree

1 file changed

+80
-3
lines changed

1 file changed

+80
-3
lines changed

Neos.Flow/Classes/ObjectManagement/Proxy/Compiler.php

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)