Skip to content
73 changes: 66 additions & 7 deletions Neos.Flow/Classes/Annotations/Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@
*/

use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Neos\Flow\ObjectManagement\Exception\ProxyCompilerException;

/**
* Used to disable proxy building for an object.
* Controls proxy class generation behavior for a class.
*
* If disabled, neither Dependency Injection nor AOP can be used
* on the object.
* This annotation allows you to:
* - Disable proxy building entirely (enabled=false) - useful for value objects, DTOs,
* or classes that should not use Dependency Injection or AOP
* - Force generation of serialization code (forceSerializationCode=true) - rarely needed
* escape hatch for edge cases where automatic detection of entity relationships fails
*
* When proxy building is disabled (enabled=false), neither Dependency Injection nor AOP
* can be used on the object. The class will be instantiated directly without any
* framework enhancements.
*
* @Annotation
* @NamedArgumentConstructor
Expand All @@ -27,13 +35,64 @@
final class Proxy
{
/**
* Whether proxy building for the target is disabled. (Can be given as anonymous argument.)
* @var boolean
* Whether proxy building is enabled for this class.
*
* When set to false, Flow will not generate a proxy class, meaning:
* - No Dependency Injection (no Flow\Inject annotations)
* - No Aspect-Oriented Programming (no AOP advices)
* - No automatic serialization handling
* - The class is instantiated directly without any framework enhancements
*
* This is useful for simple value objects, DTOs, or utility classes that don't need
* framework features and where you want to avoid the minimal overhead of proxy classes.
*
* (Can be given as anonymous argument.)
*/
public $enabled = true;
public bool $enabled = true;

public function __construct(bool $enabled = true)
/**
* Force the generation of serialization code (__sleep/__wakeup methods) in the proxy class.
*
* Flow automatically detects when serialization code is needed (e.g., when a class has entity
* properties, injected dependencies, or transient properties) and generates the appropriate
* __sleep() and __wakeup() methods. These methods handle:
* - Converting entity references to metadata (class name + persistence identifier)
* - Removing injected and framework-internal properties before serialization
* - Restoring entity references and re-injecting dependencies after deserialization
*
* This flag serves as an **escape hatch for rare edge cases** where automatic detection fails,
* such as:
* - Complex generic/template types that aren't fully parsed (e.g., ComplexType<Entity>)
* - Deeply nested entity structures where type hints don't reveal the entity relationship
* - Union or intersection types with entities that the reflection system cannot fully analyze
* - Properties with dynamic types where documentation hints are non-standard
*
* IMPORTANT: You should rarely need this flag. Flow's automatic detection handles:
* - Properties typed with Flow\Entity classes
* - Properties with Flow\Inject annotations
* - Properties with Flow\Transient annotations
* - Classes with AOP advices
* - Session-scoped objects
*
* If you find yourself needing this flag for standard entity properties, injected dependencies,
* or other common cases, this indicates a bug in Flow's detection logic that should be reported
* at https://github.com/neos/flow-development-collection/issues
*
* Note: Disabling serialization code (not possible via this flag) would break classes with
* AOP, injections, or entity relationships. To completely opt out of proxy features, use
* enabled=false instead.
*
* @see https://flowframework.readthedocs.io/ for more information on object serialization
*/
public bool $forceSerializationCode = false;

public function __construct(bool $enabled = true, bool $forceSerializationCode = false)
{
if ($enabled === false && $forceSerializationCode === true) {
throw new ProxyCompilerException('Cannot disable a Proxy but forceSerializationCode at the same time.', 1756813222);
}

$this->enabled = $enabled;
$this->forceSerializationCode = $forceSerializationCode;
}
}
3 changes: 2 additions & 1 deletion Neos.Flow/Classes/Aop/Builder/ProxyClassBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ public function buildProxyClass(string $targetClassName, array $aspectContainers
$proxyClass->addProperty($propertyName, var_export($propertyIntroduction->getInitialValue(), true), $propertyIntroduction->getPropertyVisibility(), $propertyIntroduction->getPropertyDocComment());
}

$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode(" if (method_exists(get_parent_class(\$this), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable([parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray'])) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n");
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode(" if (method_exists(parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable([parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray'])) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n");
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode($this->buildMethodsAndAdvicesArrayCode($interceptedMethods));
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->setVisibility(ProxyMethodGenerator::VISIBILITY_PROTECTED);

Expand All @@ -424,6 +424,7 @@ public function buildProxyClass(string $targetClassName, array $aspectContainers
PHP);
}
$proxyClass->addTraits(['\\' . AdvicesTrait::class]);
$proxyClass->addInterfaces(['\\' . Aop\ProxyInterface::class]);

$this->buildMethodsInterceptorCode($targetClassName, $interceptedMethods);

Expand Down
Loading