Skip to content
Draft
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;
}
}
5 changes: 3 additions & 2 deletions Neos.Flow/Classes/Aop/Builder/ProxyClassBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,21 +409,22 @@
$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);

$callBuildMethodsAndAdvicesArrayCode = "\n \$this->Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n";
$proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);
$proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);
$proxyClass->getMethod('__clone')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);
$proxyClass->getMethod('__clone')->setReturnType('void')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);

Check failure on line 419 in Neos.Flow/Classes/Aop/Builder/ProxyClassBuilder.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test static analysis (deps: highest)

Call to an undefined method Laminas\Code\Generator\MethodGenerator::addPreParentCallCode().

if (!$this->reflectionService->hasMethod($targetClassName, '__wakeup')) {
$proxyClass->getMethod('__wakeup')->addPostParentCallCode(<<<PHP
if (method_exists(get_parent_class(\$this), '__wakeup') && is_callable([parent::class, '__wakeup'])) parent::__wakeup();
PHP);
}
$proxyClass->addTraits(['\\' . AdvicesTrait::class]);
$proxyClass->addInterfaces(['\\' . Aop\ProxyInterface::class]);

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

Expand Down
39 changes: 8 additions & 31 deletions Neos.Flow/Classes/Cli/CommandManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,19 @@
class CommandManager
{
/**
* @var array<Command>
* @var array<Command>|null
*/
protected $availableCommands = null;
protected array|null $availableCommands = null;

/**
* @var array
* @var array|null
*/
protected $shortCommandIdentifiers = null;
protected array|null $shortCommandIdentifiers = null;

/**
* @var Bootstrap
*/
protected $bootstrap;

/**
* @var ObjectManagerInterface
*/
protected $objectManager;

/**
* @param ObjectManagerInterface $objectManager
* @return void
*/
public function injectObjectManager(ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

/**
* @param Bootstrap $bootstrap
* @return void
*/
public function injectBootstrap(Bootstrap $bootstrap)
{
$this->bootstrap = $bootstrap;
}
public function __construct(
private readonly Bootstrap $bootstrap,
private readonly ObjectManagerInterface $objectManager
) {}

/**
* Returns an array of all commands
Expand Down
29 changes: 4 additions & 25 deletions Neos.Flow/Classes/Cli/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,10 @@
*/
class Dispatcher
{
/**
* @var \Neos\Flow\SignalSlot\Dispatcher
*/
protected $signalDispatcher;

/**
* @var ObjectManagerInterface
*/
protected $objectManager;

/**
* @param \Neos\Flow\SignalSlot\Dispatcher $signalDispatcher
*/
public function injectSignalDispatcher(\Neos\Flow\SignalSlot\Dispatcher $signalDispatcher)
{
$this->signalDispatcher = $signalDispatcher;
}

/**
* @param ObjectManagerInterface $objectManager
*/
public function injectObjectManager(ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
public function __construct(
protected \Neos\Flow\SignalSlot\Dispatcher $signalDispatcher,
protected ObjectManagerInterface $objectManager
) {}

/**
* Try processing the request until it is successfully marked "dispatched"
Expand Down
61 changes: 6 additions & 55 deletions Neos.Flow/Classes/Cli/RequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,61 +45,12 @@ class RequestBuilder
)
/x';

/**
* @var Environment
*/
protected $environment;

/**
* @var ObjectManagerInterface
*/
protected $objectManager;

/**
* @var PackageManager
*/
protected $packageManager;

/**
* @var CommandManager
*/
protected $commandManager;

/**
* @param Environment $environment
* @return void
*/
public function injectEnvironment(Environment $environment)
{
$this->environment = $environment;
}

/**
* @param ObjectManagerInterface $objectManager
* @return void
*/
public function injectObjectManager(ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

/**
* @param PackageManager $packageManager
* @return void
*/
public function injectPackageManager(PackageManager $packageManager)
{
$this->packageManager = $packageManager;
}

/**
* @param CommandManager $commandManager
* @return void
*/
public function injectCommandManager(CommandManager $commandManager)
{
$this->commandManager = $commandManager;
}
public function __construct(
protected Environment $environment,
protected ObjectManagerInterface $objectManager,
protected PackageManager $packageManager,
protected CommandManager $commandManager
) {}

/**
* Builds a CLI request object from a command line.
Expand Down
3 changes: 0 additions & 3 deletions Neos.Flow/Classes/Command/CoreCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,8 @@ public function compileCommand(bool $force = false)
$classesCache = $this->cacheManager->getCache('Flow_Object_Classes');
$logger = $this->objectManager->get(PsrLoggerFactoryInterface::class)->get('systemLogger');

$this->proxyClassCompiler->injectClassesCache($classesCache);

$this->aopProxyClassBuilder->injectObjectConfigurationCache($objectConfigurationCache);
$this->aopProxyClassBuilder->injectLogger($logger);
$this->dependencyInjectionProxyClassBuilder->injectLogger($logger);
$this->aopProxyClassBuilder->build();
$this->dependencyInjectionProxyClassBuilder->build();

Expand Down
2 changes: 0 additions & 2 deletions Neos.Flow/Classes/Core/Booting/Scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,6 @@ public static function initializeObjectManagerCompileTimeFinalize(Bootstrap $boo
$logger = $bootstrap->getEarlyInstance(PsrLoggerFactoryInterface::class)->get('systemLogger');
$packageManager = $bootstrap->getEarlyInstance(PackageManager::class);

$objectManager->injectAllSettings($configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS));
$objectManager->injectReflectionService($reflectionService);
$objectManager->injectConfigurationManager($configurationManager);
$objectManager->injectConfigurationCache($cacheManager->getCache('Flow_Object_Configuration'));
Expand All @@ -510,7 +509,6 @@ public static function initializeObjectManager(Bootstrap $bootstrap)

$objectManager = new ObjectManager($bootstrap->getContext());

$objectManager->injectAllSettings($configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS));
$objectManager->setObjects($objectConfigurationCache->get('objects'));

foreach ($bootstrap->getEarlyInstances() as $objectName => $instance) {
Expand Down
2 changes: 1 addition & 1 deletion Neos.Flow/Classes/Monitor/FileMonitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class FileMonitor
* @param string $identifier Name of this specific file monitor - will be used in the signals emitted by this monitor.
* @api
*/
public function __construct($identifier)
public function __construct(string $identifier)
{
$this->identifier = $identifier;
}
Expand Down
6 changes: 1 addition & 5 deletions Neos.Flow/Classes/Mvc/DispatchMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
*/
class DispatchMiddleware implements MiddlewareInterface
{
/**
* @Flow\Inject(lazy=false)
* @var Dispatcher
*/
protected $dispatcher;
public function __construct(protected Dispatcher $dispatcher) {}

/**
* Create an action request from stored route match values and dispatch to that
Expand Down
21 changes: 5 additions & 16 deletions Neos.Flow/Classes/Mvc/ViewConfigurationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,11 @@
*/
class ViewConfigurationManager
{
/**
* @var VariableFrontend
*/
protected $cache;

/**
* @Flow\Inject
* @var ConfigurationManager
*/
protected $configurationManager;

/**
* @Flow\Inject
* @var CompilingEvaluator
*/
protected $eelEvaluator;
public function __construct(
protected ConfigurationManager $configurationManager,
protected CompilingEvaluator $eelEvaluator,
protected VariableFrontend $cache
) {}

/**
* This method walks through the view configuration and applies
Expand Down
Loading
Loading