flow-development-collection icon indicating copy to clipboard operation
flow-development-collection copied to clipboard

BUG: Dto with Dto in constructor creates proxy

Open mhsdesign opened this issue 2 years ago • 3 comments

Given the following code, i would expect no proxy class to be generated for either Dto or OtherDto

// no proxy class?
final readonly class Dto
{
     private function __construct(public string $bar) {}

     public static function fromString(string $bar) { return new self($bar); }
}

// no proxy class? -> or maybe yes because of a non straight value?
final readonly class OtherDto
{
     private function __construct(public Dto $dto) {}

     public static function fromString(Dto $dto) { return new self($dto); }
}

For Dto this is true, but there will be a proxy class for OtherDto, which causes problems like not being able to use named arguments: https://github.com/neos/flow-development-collection/issues/3076

<?php 

declare(strict_types=1);

namespace Neos\Neos;

readonly class OtherDto_Original
{
    public function __construct(public Dto $dto) {}

    public static function fromString(Dto $dto) { return new static($dto); }
}

#
# Start of Flow generated Proxy code
#

final readonly class OtherDto extends OtherDto_Original implements \Neos\Flow\ObjectManagement\Proxy\ProxyInterface {


    /**
     * Autogenerated Proxy Method
     */
    public function __construct()
    {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
        if (isset($backtrace[1]) &&
            !is_subclass_of($backtrace[1]['class'], \Neos\Neos\OtherDto::class) &&
            !is_subclass_of(\Neos\Neos\OtherDto::class, $backtrace[1]['class']) &&
            $backtrace[1]['class'] !== 'Neos\Neos\OtherDto' &&
            $backtrace[1]['class'] !== 'Neos\Neos\OtherDto_Original'
        ) {
            throw new \Error('Call to private Neos\Neos\OtherDto::__construct() from invalid context', 1686153840);
        }
        $arguments = func_get_args();


        if (!array_key_exists(0, $arguments)) $arguments[0] = \Neos\Flow\Core\Bootstrap::$staticObjectManager->get('Neos\Neos\Dto');
        if (!array_key_exists(0, $arguments)) throw new \Neos\Flow\ObjectManagement\Exception\UnresolvedDependenciesException('Missing required constructor argument $dto in class ' . __CLASS__ . '. Note that constructor injection is only support for objects of scope singleton (and this is not a singleton) – for other scopes you must pass each required argument to the constructor yourself.', 1296143788);
        parent::__construct(...$arguments);
    }
}

mhsdesign avatar Nov 01 '23 10:11 mhsdesign

Since this issue is already part of the Flow Proxy Refactoring board, I just wanted to mention, that this causes an Exception with current Neos 9 implementation during node creation.

What I've done

  1. Delete all tables in the DB
  2. composer update
  3. rm -rf Data/Temporary
  4. ./flow flow:cache:flush --force # just in case
  5. ./flow flow:package:rescan
  6. ./flow doctrine:migrate
  7. ./flow cr:setup
  8. ./flow site:create neosdemo Neos.Demo Neos.Demo:Document.Homepage
  9. ./flow user:create --roles Neos.Neos:Administrator --username user --first-name us --last-name er
  10. Open Neos in Browser - you see the Demo page
  11. Login to Neos
  12. In neosdemo (=Home) > Teaser area create a new Text Element inside

A red alert is shown Unknown named parameter $elementValues - Check the logs for details

Error details

From System_Development.log:

24-04-26 11:49:42 53         CRITICAL                       Exception in line 167 of /application/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/Neos_Neos_Ui_Domain_Model_Changes_AbstractCreate.php: Unknown named parameter $elementValues - See also: 202404261149424c0ba0.txt
The exception:
Exception in line 167 of /application/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/Neos_Neos_Ui_Domain_Model_Changes_AbstractCreate.php: Unknown named parameter $elementValues

56 Neos\Neos\Ui\Domain\Service\NodePropertyConversionService_Original::convertNodeCreationElements()
55 Neos\Neos\Ui\Domain\Model\Changes\AbstractCreate_Original::createNode()
54 Neos\Neos\Ui\Domain\Model\Changes\Create_Original::apply()
53 Neos\Neos\Ui\Domain\Model\ChangeCollection_Original::apply()
52 Neos\Neos\Ui\Controller\BackendServiceController_Original::changeAction()
51 Neos\Neos\Ui\Controller\BackendServiceController::changeAction()
50 Neos\Neos\Ui\Controller\BackendServiceController::Flow_Aop_Proxy_invokeJoinPoint()
49 Neos\Flow\Aop\Advice\AdviceChain::proceed()
48 Neos\Flow\Security\Aspect\PolicyEnforcementAspect_Original::enforcePolicy()
47 Neos\Flow\Aop\Advice\AroundAdvice::invoke()
46 Neos\Flow\Aop\Advice\AdviceChain::proceed()
45 Neos\Neos\Ui\Controller\BackendServiceController::changeAction()
44 Neos\Flow\Mvc\Controller\ActionController_Original::callActionMethod()
43 Neos\Neos\Ui\Controller\BackendServiceController::callActionMethod()
42 Neos\Neos\Ui\Controller\BackendServiceController::Flow_Aop_Proxy_invokeJoinPoint()
41 Neos\Flow\Aop\Advice\AdviceChain::proceed()
40 Neos\Flow\Security\Aspect\PolicyEnforcementAspect_Original::enforcePolicy()
39 Neos\Flow\Aop\Advice\AroundAdvice::invoke()
38 Neos\Flow\Aop\Advice\AdviceChain::proceed()
37 Neos\Neos\Ui\Controller\BackendServiceController::callActionMethod()
36 Neos\Flow\Mvc\Controller\ActionController_Original::processRequest()
35 Neos\Neos\Ui\Controller\BackendServiceController::processRequest()
34 Neos\Neos\Ui\Controller\BackendServiceController::Flow_Aop_Proxy_invokeJoinPoint()
33 Neos\Flow\Aop\Advice\AdviceChain::proceed()
32 Neos\Flow\Security\Aspect\PolicyEnforcementAspect_Original::enforcePolicy()
31 Neos\Flow\Aop\Advice\AroundAdvice::invoke()
30 Neos\Flow\Aop\Advice\AdviceChain::proceed()
29 Neos\Neos\Ui\Controller\BackendServiceController::processRequest()
28 Neos\Flow\Mvc\Dispatcher_Original::initiateDispatchLoop()
27 Neos\Flow\Mvc\Dispatcher_Original::dispatch()
26 Neos\Flow\Mvc\DispatchMiddleware_Original::process()
25 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
24 Neos\Flow\Http\Middleware\SecurityEntryPointMiddleware_Original::process()
23 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
22 Neos\Flow\Http\Middleware\RequestBodyParsingMiddleware_Original::process()
21 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
20 Neos\Flow\Mvc\FlashMessage\FlashMessageMiddleware_Original::process()
19 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
18 Neos\Flow\Http\Middleware\PoweredByMiddleware_Original::process()
17 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
16 Neos\Flow\Mvc\Routing\RoutingMiddleware_Original::process()
15 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
14 Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionMiddleware_Original::process()
13 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
12 Neos\FluidAdaptor\Core\Widget\AjaxWidgetMiddleware_Original::process()
11 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
10 Neos\Flow\Http\Middleware\SessionMiddleware_Original::process()
9 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
8 Neos\Flow\Http\Middleware\MethodOverrideMiddleware_Original::process()
7 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
6 Neos\Flow\Http\Middleware\TrustedProxiesMiddleware_Original::process()
5 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
4 Neos\Flow\Http\Middleware\StandardsComplianceMiddleware_Original::process()
3 Neos\Flow\Http\Middleware\MiddlewaresChain_Original::handle()
2 Neos\Flow\Http\RequestHandler::handleRequest()
1 Neos\Flow\Core\Bootstrap::run()


HTTP REQUEST:
target: /neos/ui-services/change
Host: playground001.webco.pw
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0
Content-Length: 544
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Cookie: Neos_Session=yvDQyeLHq17k77CNPmOSXHHv4dML4usA
Origin: http://playground001.webco.pw
Referer: http://playground001.webco.pw/neos/content?node=user-gj__eyJsYW5ndWFnZSI6ImVuX1VTIn0%3D__6244183f-5027-4efa-9f0d-a06dd97a6d58
X-Flow-Csrftoken: 144f8ba50bf406550c282933651944bd
X-Forwarded-For: 172.17.21.1
X-Forwarded-Host: playground001.webco.pw
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 7a183e19853d
X-Real-Ip: 172.17.21.1

PHP PROCESS:
Inode: 5241236
PID: 53
UID: 1000
GID: 1000
User: www-data

Environment

I'm using php 8.2.4 in a docker container, Mariadb 11.0.2 and current up to date Neos 9 packages:

neos/neos-development-collection   9.0.x-dev dab3d0b  Neos packages in a joined repository for pull requests.
neos/neos-ui                       9.0.x-dev 2485e9e  Neos CMS UI written in React
neos/neos-ui-compiled              9.0.x-dev e016abd 
neos/flow-development-collection   9.0.x-dev 00fa8ff  Flow packages in a joined repository for pull requests.

Further details

The reason, as correctly identified by @mhsdesign here, is, that Flow creates a proxy class for NodeCreationElements. The proxy class does not have named constructor arguments. Thus NodePropertyConversionService::convertNodeCreationElements fails with the shown php fatal.
Slightly unrelated, there is also a proxy class for NodeCreationCommands. Not sure if this information is of any value.

gjwnc avatar Apr 26 '24 10:04 gjwnc

Just wanted to mention, that my specific problem above seems to be gone with neos 9-beta19.

gjwnc avatar Apr 01 '25 10:04 gjwnc

THanks, i fixed the neos ui problem via https://github.com/neos/neos-ui/commit/d9721fd069b1e9b6745b26fb7006ea1a41912a06

mhsdesign avatar Apr 01 '25 15:04 mhsdesign

I started implementing fix but then realized: we did discuss that a lot already in 2023 which lead to #3049:

Flow cannot reliably detect weather a prototype class depends on autowiring for constructor arguments or not. Use this option to optimize your application to avoid the small but measurable overhead of proxy generation for those kinds of classes.

You are right that this is related to #3076 - but to be exact, that's a different issue which need to be solved separately.

Therefore, I suggest we close this issue.

robertlemke avatar Oct 20 '25 18:10 robertlemke

This won't be possible to implement without breaking changes. A good solution would be to not proxy readonly or final classes or private constructors. We can look into that for Flow 10.0.

robertlemke avatar Oct 20 '25 18:10 robertlemke