-
-
Notifications
You must be signed in to change notification settings - Fork 189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FEATURE: Lazy injections for typed properties #2701
base: 8.2
Are you sure you want to change the base?
Conversation
2ef2e34
to
de0c42b
Compare
de0c42b
to
49ccdc1
Compare
The code works now and should solve the problem at hand. What's still missing are the respective test adjustments. |
This is now ready for reviews. |
Sigh, PHP 7.3 fails. So I either need to scrub this more, so that it doesn't fail with PHP 7.3, or don't release this in Flow 7.3 but only 8.0. Let's discuss this here: https://discuss.neos.io/t/rfc-drop-php-7-3-support-in-all-future-flow-versions/5844 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look at the 7.3 failures this evening. Looks as if the generated code for the proxies is just slightly off, though not sure why only on 7.3? Maybe a different laminas version is installed there with slightly different behaviour?
This change adjusts the proxy building and dependency injection process to properly handle PHP 7.4 class property type declarations. neos#2114
This change makes sure that a reflected class property type does not contain a "?" prefix when the property is nullable.
55aa09c
to
069d1c3
Compare
This change is now targeted and rebased for master. |
Thanks, @kdambekalns! At this point all pending questions are answered and code suggestions were committed. |
@robertlemke i really want to approve this since the syntax is much much much much fancier ... and cooler ^^ but i found out in my tests that this really increases the time to get flow ready from 0 - 100. so i directly noticed a performance increase while doing functional Fusion tests (i was trying out your new functionality there) to make this more comparable i flushed the caches and manually timed how long
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imagine i have this code: (DslFactory is a singelton)
/**
* @Flow\Inject
* @var DslFactory
*/
protected $dslFactory;
i now want to use typed properties, so i naively do so first:
/**
* @Flow\Inject
*/
protected DslFactory $dslFactory;
and get some cryptic exception about: code line:
Cannot access uninitialized non-nullable property Neos\Fusion\Core\Parser_Original::$dslFactory by reference
DependencyInjection/PropertyInjectionTrait.php:32
then i go back to your nice PR body and ahh i need to use ?
so i do so.
/**
* @Flow\Inject
*/
protected ?DslFactory $dslFactory;
i think we should have a better exception message than the of php. -> thats easy.
but the problem is my code doesnt work now directly, i needed to flush the caches - what takes some time longer and then it works.
in short:
do this:
/**
* @Flow\Inject
* @var DslFactory
*/
protected $dslFactory;
run flow ...
/**
* @var DslFactory
*/
protected $dslFactory;
run flow ... (doesnt work)
/**
* @Flow\Inject
* @var DslFactory
*/
protected $dslFactory;
run flow ... flow@master
works here, but flow@robert
doesnt... in my case the DslFactory
had no working @Flow\InjectConfiguration
and the $objectManger wasnt injected too (so no working DI) after a subtle change in the DslFactory File the injection was working again too (or after cache flush).
so at the moment this feels sometimes buggy - just go around fx. in the neos dev collection and try to change to typed properties here and there -> you will often find yourself in need to make a cache flush so the proxybuilding is properly working again. but @robertlemke i would like to help tommorow to make this working! |
@mhsdesign very good catch! Here are my timings for comparison:
I did a quick first profiling and apparently, the Reflection Service is the culprit. From a total wall time of 15.2 seconds (it takes longer because of the profiling engine), my branch is more than 6 seconds slower while reflecting classes. I'll take a closer look and see where code can be optimized. |
If someone is reviewing this right now – please postpone your review a little. I need to rework some of the internals due to the performance issues. |
do i see this right that using |
Mostly, yes. But it's also a handy API which allows you to pass an existing class reflection and then modify code through certain setters. The idea was to transition to a library (Laminas) instead of doing all that on our own (like we did before). But unfortunately, it doesn't work (fast enough) out of the box. |
# lazy loading is implemented. If any of the check fails, we fall back to eager loading: | ||
if ( | ||
$propertyConfiguration->isLazyLoading() && | ||
$this->objectConfigurations[$propertyObjectName]->getScope() !== Configuration::SCOPE_PROTOTYPE && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We dont allow prototypes lazly injected - but on my pc there are tousands of lazy proxies for prototypes generated ... isnt this a waste of performance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, definitely a thing to optimize before merging.
$method->setBody( | ||
<<< CODE | ||
\$arguments = func_get_args(); | ||
{$returnKeyword}\$this->_activateDependency()->{$methodName}(...\$arguments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesnt work for static methods ... (Using '$this' when not in object context)
noticed when looking at some random proxy files:
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, \Bar $bar)
{
$arguments = func_get_args();
return $this->_activateDependency()->renderStatic(...$arguments);
}
what should we do in that case?
this?
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, \Bar $bar)
{
$arguments = func_get_args();
return self::renderStatic(...$arguments);
}
{ | ||
$injection_reference = &$this->$propertyName; | ||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getInstance($propertyObjectName); | ||
if ($this->$propertyName === null) { | ||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getLazyDependencyByHash($setterArgumentHash, $injection_reference); | ||
if ($this->$propertyName === null) { | ||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->createLazyDependency($setterArgumentHash, $injection_reference, $propertyClassName, $lazyInjectionResolver); | ||
if ($this->$propertyName === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we allow with this change less lazy dependency injection that before?
related: interfaces fx. cant be injected lazily anymore? #2701 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it just make sure that if a dependency can not be injected lazily (createLazyDependency returns null), it falls back to eager injection
@@ -27,14 +27,18 @@ trait PropertyInjectionTrait | |||
* @param callable $lazyInjectionResolver | |||
* @return void | |||
*/ | |||
private function Flow_Proxy_LazyPropertyInjection($propertyObjectName, $propertyClassName, $propertyName, $setterArgumentHash, callable $lazyInjectionResolver) | |||
private function Flow_Proxy_LazyPropertyInjection(string $propertyObjectName, string $propertyClassName, string $propertyName, string $setterArgumentHash, callable $lazyInjectionResolver) | |||
{ | |||
$injection_reference = &$this->$propertyName; | |||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getInstance($propertyObjectName); | |||
if ($this->$propertyName === null) { | |||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getLazyDependencyByHash($setterArgumentHash, $injection_reference); | |||
if ($this->$propertyName === null) { | |||
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->createLazyDependency($setterArgumentHash, $injection_reference, $propertyClassName, $lazyInjectionResolver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i finally kindof found the reson for this buggy behavior that on the fly di changes dont work correctly:
#2701 (review)
it seems that this method createLazyDependency
is returning null
and the next call: $staticObjectManager->get($propertyObjectName);
doesnt return an object of the proxied class but rather an object based of the class in my working dir...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so #2701 (review) even shorter:
go to a lazy dependency injection (in the old doc comment style - so you can compare this exact behaviour with flow@master
)
- remove the
@Flow\Inject
and run a flow - add the
@Flow\Inject
again and run flow again -> now the property should be injected.
but at flow@robert
as i seems this code:
flow-development-collection/Neos.Flow/Classes/ObjectManagement/ObjectManager.php
Line 420 in aea746a
// If proxy classes were partially flushed in Development context, it might happen that |
is triggered but flow@master
is working in this scenario
I think I can't help much, but I want to thank you for all your effort you put into this ❤️ |
@fcool thought me that it works already like this: #[Flow\Inject(name: BlaInterface::class, lazy: true)]
protected BlaInterface|DependencyProxy|null $bla; it would be great of course if i could leave out the (redundant) |
I kindoff like this style. Makes the lazyness very explicit and opt in. For me it would be fine to document this. |
me too (especially cases, where youd need to pass the dependency it becomes clear why youd need to activate it first), but i would like to get rid of the extra arguments so we can write: #[Flow\Inject()]
protected BlaInterface|DependencyProxy|null $bla; |
This change adjusts the proxy building and dependency injection process to properly handle PHP 7.4 class property type declarations.
It resolves #2114
Also resolve #2601
How to verify it
What did not work before this change was using property type declarations for lazily injected properties.
Consider the following code: injecting
$testSingleton
is what this fix makes possible.In order to verify this change, you can use the test package (see zip file) and play a bit with the injected properties. Things to try out (at least):
./flow test:test
with an empty cacheAcme.LazyTest.zip