-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Lazy services - service proxies #7527
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
Conversation
@jrobeson pointed me here. The functionality definitely sounds valuable. I know Lukas has been talking about this for a long time. However, the dependency chain here is not fun. I cannot speak for Fabien, but I'd be wary of adding a dependency on 2 additional libraries to DependencyInjection just for this functionality. Even that aside, the cg-library package is licensed under Apache 2. That's a problem. Specifically, it means that the DI Component cannot run without Apache 2 code, which in turn means that it has the same impact license-wise as being Apache 2. That is, it becomes incompatible with GPLv2 and GPLv2+ code. While Symfony itself is not GPL licensed, many of the systems that are now depending on it are. In particular, Drupal and phpBB are both GPLv2+ licensed. GPLv2 is incompatible with Apache 2. That would make the DI component, and by extension Symfony fullstack, unusable for both Drupal and phpBB, as well as likely others. (I don't know the license of any of the other Symfony-based systems off hand.) It also makes it unsuable for bespoke systems by shops that use GPLv2 (and there are a lot of them) that want to build off of Symfony2. So, yeah. Introducing an Apache2 dependency would be a massive license-breaking and ecosystem-crippling change. :-/ jrobeson indicated that there was an alternative library, or that the cg-library could be relicensed if we convince the maintainer to do so. Either way the licensing issue would go away. The question of introducing additional dependencies I leave to fabpot. Disclaimer: I'm the former Director of Legal Affairs for the Drupal Association, so I've spent a fair bit of time looking into this subject. I'm not just making it up. :-) |
@Crell I've been told that today, and didn't reallly expect it (I have no idea whatsoever this involves, I write code :( ). If BSD-3-Clause is compliant, I can convert my library to use |
@Crell I removed usage of cg-lib in ProxyManager as of Ocramius/ProxyManager#18 - should be ok now |
Do you have any benchmark on a "classic" Symfony2 application ? (without |
@lyrixx no, I actually don't use Symfony SE. You can actually benchmark it yourself by insulating a portion of your object graph that is not used and marking the root node of that portion as lazy. From what I know, the security component should be quite large. Another thing this tries to remove is excessive usage of service location for performance optimizations, as I've explained here. You can also read a detailed description of how lazy loading proxies (as implemented here) work here OOTB, as it is now, this PR does not introduce any overhead. |
@@ -861,8 +864,24 @@ public function findDefinition($id) | |||
* @throws RuntimeException When the service is a synthetic service | |||
* @throws InvalidArgumentException When configure callable is not callable | |||
*/ | |||
private function createService(Definition $definition, $id) | |||
public function createService(Definition $definition, $id, $tryProxy = true) |
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.
you need to update the phpdoc
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.
And you should add @internal
in the phpdoc to explain that it is only public because of the internal use in a closure, and should not be called by other code
Your current implementation breaks on 1 point: recreating the proxy class when the original class changes. The class has to be added as a container resource for lazy services so that the container is dumped again when the class changes, so that the proxies are dumped again. Otherwise, the proxy might not match the class anymore. What you would need is the logic of |
For the ContainerBuilder case, using |
Your code has a big issue: it creates a new proxy instance each time the service is retrieved from the container instead of reusing the same instance for container-scoped services. So |
thus, getting the lazy service, then doing a method call (thus initializing the proxy) and then getting it again would give you the wrapped instance after that, being inconsistent. |
@stof very good catch! Couldn't write a for that test because of the conflicting container names. I'll see if I can change the name of a generated container name and write something that covers that. |
Also note, that the usage of eval may make the symfony2 projects that use lazy loading unusable on some shared hosts. |
@mvrhov ouch! Do they turn it off? I can build a switch for that eventually if there's a way of recognizing it. |
yeah. I worked for one a few years ago and we were disabling it. The problem was that that a large amount of code injections that were happening then abused eval to send a spam or worse. Trying to get unlisted from the sites that list spam sending IPs every few weeks also become troublesome. |
@mvrhov I spawned Ocramius/ProxyManager#24 from that. It should not be handled in symfony itself. |
@stof can you clarify when I should add a resource? I added |
The feature by itself would be a great addition but I agree with @Crell that the added dependencies are not so good. Besides, the generated class names of proxies should follow the naming convention used by both the CG library and Doctrine (with a |
To clarify, I modified the PR so that the dependencies are now optional. @jalliot for CG Lib, I kicked that out and replaced it with That's anyway stuff that doesn't affect this PR's code directly. SF2 just benefits from the abstraction layer of ProxyManager and should not really care about how that is achieved except for BC breaks, performance and security issues (and license compatibility, as introduced by @Crell). For If you want to identify a ProxyManager proxy, you can use the utility within ProxyManager itself. An idea would be to drop the |
use ProxyManager\Factory\LazyLoadingValueHolderFactory; | ||
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; | ||
use ProxyManager\Proxy\LazyLoadingInterface; | ||
use ReflectionClass; |
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.
Symfony does not add use statements for classes in the global namespace
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.
Will revert :)
its an optional dependency but i agree that adding 2 more dependencies can be a concern. doctrine proxy and the cg lib are commonly used already in the Symfony2 ecosystem while proxy manager and zend code are less used. i didnt check but ideally the code should maybe gracefully ignore the lazy setting in case the dependencies are missing so that no code would break if those optional deps are not installed. |
@lsmith77 perfect, I will add the code required to let any lazy markers being ignored in the case where dependencies are not loaded. |
* | ||
* @api | ||
*/ | ||
public function addClassResource(\ReflectionClass $class) |
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.
why not passing a class name and building the ReflectionClass internally ? addObjectResource
expects an object, not a ReflectionObject
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.
Strict typing. This disallows unexisting classes upfront
Still need to fix the 5.3 failures |
@@ -11,6 +11,9 @@ | |||
|
|||
namespace Symfony\Component\DependencyInjection\Dumper; | |||
|
|||
use ProxyManager\Generator\ClassGenerator; |
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.
Again here, can we abstract this against an interface to not violate the DIP? This dependency is optional, but the code makes it "required".
To clarify my comments above, the proxy manager is an optional dependency and should therefore be hidden in an adapter, maybe even in its own Bridge, implementing a generic interface from DependencyInjection component |
Agreed - fixing later today |
closing in favor of #7890 |
This PR was squashed before being merged into the master branch (closes #7890). Discussion ---------- ProxyManager Bridge As of @beberlei's suggestion, I re-implemented #7527 as a new bridge to avoid possible hidden dependencies. Everything is like #7527 except that the new namespace (and possibly package/subtree split) `Symfony\Bridge\ProxyManager` is introduced | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #6140 (supersedes) #5012 #6102 (maybe) #7527 (supersedes) | License | MIT (attached code) - BSD-3-Clause (transitive dependency) | Doc PR | Please pester me to death so I do it This PR introduces lazy services along the lines of zendframework/zendframework#4146 It introduces an **OPTIONAL** dependency to [ProxyManager](https://github.com/Ocramius/ProxyManager) and transitively to [`"zendframework/zend-code": "2.*"`](https://github.com/zendframework/zf2/tree/master/library/Zend/Code). ## Lazy services: why? A comprehensive example For those who don't know what this is about, here's an example. Assuming you have a service class like following: ```php class MySuperSlowClass { public function __construct() { // inject large object graph or do heavy computation sleep(10); } public function doFoo() { echo 'Foo!'; } } ``` The DIC will hang for 10 seconds when calling: ```php $container->get('my_super_slow_class'); ``` With this PR, this can be avoided, and the following call will return a proxy immediately. ```php $container->getDefinitions('my_super_slow_class')->setLazy(true); $service = $container->get('my_super_slow_class'); ``` The 10 seconds wait time will be delayed until the object is actually used: ```php $service->doFoo(); // wait 10 seconds, then 'Foo!' ``` A more extensive description of the functionality can be found [here](https://github.com/Ocramius/ProxyManager/blob/master/docs/lazy-loading-value-holder.md). ## When do we need it? Lazy services can be used to optimize the dependency graph in cases like: * Webservice endpoints * Db connections * Objects that cause I/O in general * Large dependency graphs that are not always used This could also help in reducing excessive service location usage as I've explained [here](http://ocramius.github.com/blog/zf2-and-symfony-service-proxies-with-doctrine-proxies/). ## Implementation quirks of this PR There's a couple of quirks in the implementation: * `Symfony\Component\DependencyInjection\CompilerBuilder#createService` is now public because of the limitations of PHP 5.3 * `Symfony\Component\DependencyInjection\Dumper\PhpDumper` now with extra mess! * The proxies are dumped at the end of compiled containers, therefore the container class is not PSR compliant anymore Commits ------- 78e3710 ProxyManager Bridge
This PR introduces lazy services along the lines of zendframework/zendframework#4146
It introduces an OPTIONAL dependency to ProxyManager and transitively to
"zendframework/zend-code": "2.*"
.Lazy services: why? A comprehensive example
For those who don't know what this is about, here's an example.
Assuming you have a service class like following:
The DIC will hang for 10 seconds when calling:
With this PR, this can be avoided, and the following call will return a proxy immediately.
The 10 seconds wait time will be delayed until the object is actually used:
A more extensive description of the functionality can be found here.
When do we need it?
Lazy services can be used to optimize the dependency graph in cases like:
This could also help in reducing excessive service location usage as I've explained here.
Implementation quirks of this PR
There's a couple of quirks in the implementation:
Symfony\Component\DependencyInjection\CompilerBuilder#createService
is now public because of the limitations of PHP 5.3Symfony\Component\DependencyInjection\Dumper\PhpDumper
now with extra mess!Todos
eval
eval
case