You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR was merged into the 6.2 branch.
Discussion
----------
[DependencyInjection][VarExporter] Generate lazy-loading virtual proxies for non-ghostable lazy services
| Q | A
| ------------- | ---
| Branch? | 6.2
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | -
| License | MIT
| Doc PR | -
Since #46752 and #46751, we are able to make services lazy out of the box, except when 1. a service relies on an internal class 2. a service has the `proxy` tag or 3. a service's class is abstract (and the service uses a factory). In these situations, proxy-manager-bridge was required. This was an acceptable trade-off because this would be quite uncommon. But while working on Doctrine, I realized that we cannot use ghost objects when a factory is used. I described this for Doctrine in doctrine/orm#9896 but the situation can happen with any services constructed by a factory. This means we'd need proxy-manager-bridge anytime a factory is used on a lazy service. What was uncommon becomes quite common and the trade-off is not acceptable anymore. Thus this PR.
This PR adds a `LazyProxyTrait` and a `ProxyHelper` to build lazy loading virtual proxies at will. It then wires this new capability into the container. As a result proxy-manager-bridge is not needed anymore. We can deprecate it in another PR.
While the diff is quite big, `LazyProxyTrait` has many similarities with `LazyGhostTrait` and both traits can be diffed to see where their behavior varies.
Excerpt from the [README](https://github.com/nicolas-grekas/symfony/blob/ve-inheritance-proxy/src/Symfony/Component/VarExporter/README.md) for the record:
>The component provides two lazy loading patterns: ghost objects and virtual proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference.)
>
>Ghost objects work only on concrete and non-internal classes. In the generic case, they are not compatible with using factories in their initializer.
>
>Virtual proxies work on concrete, abstract or internal classes. They provide an API that looks like the actual objects and forward calls to them. They can cause identity problems because proxies might not be seen as equivalents to the actual objects.
>
>Because of this identity problem, ghost objects should be preferred when possible. Exceptions thrown by the ProxyHelper class can help decide when it can be used or not.
>
>Ghost objects and virtual proxies both provide implementations for the LazyObjectInterface which allows resetting them to their initial state or to forcibly initialize them when needed. Note that resetting a ghost object skips its read-only properties. You should use a virtual proxy to reset read-only properties.
Commits
-------
4862139 [DependencyInjection][VarExporter] Generate lazy proxies for non-ghostable lazy services out of the box
thrownewAutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
* ContainerBuilder is a DI container that provides an API to easily describe services.
@@ -1037,10 +1036,6 @@ private function createService(Definition $definition, array &$inlineServices, b
1037
1036
if (null !== $factory) {
1038
1037
$service = $factory(...$arguments);
1039
1038
1040
-
if (\is_object($tryProxy) && $service::class !== $parameterBag->resolveValue($definition->getClass())) {
1041
-
thrownewLogicException(sprintf('Lazy service of type "%s" cannot be hydrated because its factory returned an unexpected instance of "%s". Try adding the "proxy" tag to the corresponding service definition with attribute "interface" set to "%1$s".', $definition->getClass(), get_debug_type($service)));
1042
-
}
1043
-
1044
1039
if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
1045
1040
$r = new \ReflectionClass($factory[0]);
1046
1041
@@ -1110,10 +1105,6 @@ private function createService(Definition $definition, array &$inlineServices, b
1110
1105
$callable($service);
1111
1106
}
1112
1107
1113
-
if (\is_object($tryProxy) && $tryProxy !== $service) {
@@ -1326,19 +1317,6 @@ protected function createProxy(\$class, \Closure \$factory)
1326
1317
{$proxyLoader}return \$factory();
1327
1318
}
1328
1319
1329
-
protected function hydrateProxy(\$proxy, \$instance)
1330
-
{
1331
-
if (\$proxy === \$instance) {
1332
-
return \$proxy;
1333
-
}
1334
-
1335
-
if (!\in_array(\get_class(\$instance), [\get_class(\$proxy), get_parent_class(\$proxy)], true)) {
1336
-
throw new LogicException(sprintf('Lazy service of type "%s" cannot be hydrated because its factory returned an unexpected instance of "%s". Try adding the "proxy" tag to the corresponding service definition with attribute "interface" set to "%1\$s".', get_parent_class(\$proxy), get_debug_type(\$instance)));
0 commit comments