Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

[DependencyInjection] allows references to be declared as "lazy" #6140

Closed
wants to merge 1 commit into from

7 participants

@schmittjoh

I had written this patch during sflive Berlin. It allows to store additional metadata in references. It contains no code to leverage that additional metadata though.

In contrast to regular references, a lazy reference is not guaranteed to be fully initialized upon injection, but may instead be lazy initialized when it is first accessed by the consuming service.

Code that uses the lazy attribute could also reside outside of the core repository as it might contain additional dependencies which we probably do not want in the core component.

@schmittjoh schmittjoh [DependencyInjection] allows references to be declared as "lazy"
In contrast to regular references, a lazy references is not guaranteed to be fully
initialized upon injection, but may instead be lazy initialized when it is first
accessed by the consuming service.

Lazy makes no implementation restrictions on how those referenced services are
to be constructed but leave this up to a concrete implementor.
afacff9
@pborreli pborreli commented on the diff
...omponent/DependencyInjection/Loader/XmlFileLoader.php
@@ -25,6 +25,7 @@
* XmlFileLoader loads XML files service definitions.
*
* @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>

why ?

I thought I was going to change the file, and saw that I had not yet added my name :) Anyway, I have made many small feature additions in this file that should warrant the tag.

If possible, I'd like to first talk about the general idea of this patch though. If we agree on that, we can also talk about when @author tags are justified, and whether this one (or others) need to be removed.

i was just curious, sorry for that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@asm89

Maybe the GraphvizDumper can also show something on the edges if a service is "lazy"?

@Ocramius

@schmittjoh do you think tests for lazy services should reside in core?

@stof
Collaborator

@schmittjoh is it meant to be used for #5012 or is it totally unrelated ?

@schmittjoh

@stof, that is what it would be used for.

@andrerom

This seems to be useful for #6102 as well.

Regarding the patch; seems strict was partly there from before, but not covered by tests. Elsewhere, or should it be added?

@schmittjoh

Yes, there were a few bugs I noticed when adding lazy.

Regarding #6102, you might want to look at http://jmsyst.com/bundles/JMSDiExtraBundle/master/lazy_service_collections. You already should have that capability if you are using the Symfony Standard Edition.

@fabpot
Owner

Closing as the discussion should now happen on #7527

@fabpot fabpot closed this
@Ocramius Ocramius referenced this pull request
Closed

ProxyManager Bridge #7890

@fabpot fabpot referenced this pull request from a commit
@fabpot fabpot merged branch Ocramius/feature/proxy-manager-bridge (PR #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/zf2#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
dfd605f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 28, 2012
  1. @schmittjoh

    [DependencyInjection] allows references to be declared as "lazy"

    schmittjoh authored
    In contrast to regular references, a lazy references is not guaranteed to be fully
    initialized upon injection, but may instead be lazy initialized when it is first
    accessed by the consuming service.
    
    Lazy makes no implementation restrictions on how those referenced services are
    to be constructed but leave this up to a concrete implementor.
This page is out of date. Refresh to see the latest.
View
9 src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -22,6 +22,7 @@
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Martin Hasoň <martin.hason@gmail.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @api
*/
@@ -239,6 +240,14 @@ private function convertParameters($parameters, $type, \DOMElement $parent, $key
} elseif ($behaviour == ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
$element->setAttribute('on-invalid', 'ignore');
}
+
+ if ( ! $value->isStrict()) {
+ $element->setAttribute('strict', 'false');
+ }
+
+ if ($value->isLazy()) {
+ $element->setAttribute('lazy', 'true');
+ }
} elseif ($value instanceof Definition) {
$element->setAttribute('type', 'service');
$this->addService($value, null, $element);
View
11 src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -24,6 +24,7 @@
* YamlDumper dumps a service container as a YAML string.
*
* @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @api
*/
@@ -212,7 +213,15 @@ private function dumpValue($value)
return $code;
} elseif ($value instanceof Reference) {
- return $this->getServiceCall((string) $value, $value);
+ $call = $this->getServiceCall((string) $value, $value);
+ if ($value->isLazy()) {
+ $call .= '~';
+ }
+ if ( ! $value->isStrict()) {
+ $call .= '=';
+ }
+
+ return $call;
} elseif ($value instanceof Parameter) {
return $this->getParameterCall((string) $value);
} elseif (is_object($value) || is_resource($value)) {
View
1  src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -25,6 +25,7 @@
* XmlFileLoader loads XML files service definitions.
*
* @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>

why ?

I thought I was going to change the file, and saw that I had not yet added my name :) Anyway, I have made many small feature additions in this file that should warrant the tag.

If possible, I'd like to first talk about the general idea of this patch though. If we agree on that, we can also talk about when @author tags are justified, and whether this one (or others) need to be removed.

i was just curious, sorry for that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
*/
class XmlFileLoader extends FileLoader
{
View
11 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -26,6 +26,7 @@
* The YAML format does not support anonymous services (cf. the XML loader).
*
* @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class YamlFileLoader extends FileLoader
{
@@ -298,14 +299,20 @@ private function resolveServices($value)
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
}
+ $strict = true;
if ('=' === substr($value, -1)) {
$value = substr($value, 0, -1);
$strict = false;
- } else {
- $strict = true;
+ }
+
+ $lazy = false;
+ if ('~' === substr($value, -1)) {
+ $value = substr($value, 0, -1);
+ $lazy = true;
}
$value = new Reference($value, $invalidBehavior, $strict);
+ $value->setLazy($lazy);
}
return $value;
View
2  src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
@@ -126,6 +126,7 @@
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="on-invalid" type="xsd:string" />
<xsd:attribute name="strict" type="boolean" />
+ <xsd:attribute name="lazy" type="boolean" />
</xsd:complexType>
<xsd:complexType name="argument" mixed="true">
@@ -139,6 +140,7 @@
<xsd:attribute name="index" type="xsd:integer" />
<xsd:attribute name="on-invalid" type="xsd:string" />
<xsd:attribute name="strict" type="boolean" />
+ <xsd:attribute name="lazy" type="boolean" />
</xsd:complexType>
<xsd:complexType name="call" mixed="true">
View
26 src/Symfony/Component/DependencyInjection/Reference.php
@@ -15,6 +15,7 @@
* Reference represents a service reference.
*
* @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @api
*/
@@ -23,6 +24,7 @@ class Reference
private $id;
private $invalidBehavior;
private $strict;
+ private $lazy = false;
/**
* Constructor.
@@ -69,4 +71,28 @@ public function isStrict()
{
return $this->strict;
}
+
+ /**
+ * Whether this reference should be lazily initialized if available.
+ *
+ * @return Boolean
+ */
+ public function isLazy()
+ {
+ return $this->lazy;
+ }
+
+ /**
+ * Sets the lazily initialization status for this reference.
+ *
+ * @param Boolean $bool
+ *
+ * @return Reference
+ */
+ public function setLazy($bool)
+ {
+ $this->lazy = $bool;
+
+ return $this;
+ }
}
View
5 src/Symfony/Component/DependencyInjection/SimpleXMLElement.php
@@ -74,6 +74,11 @@ public function getArgumentsAsPhp($name, $lowercase = true)
}
$arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict);
+
+ if (isset($arg['lazy'])) {
+ $arguments[$key]->setLazy(self::phpize($arg['lazy']));
+ }
+
break;
case 'collection':
$arguments[$key] = $arg->getArgumentsAsPhp($name, false);
View
4 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml
@@ -46,5 +46,9 @@
<service id="alias_for_foo" alias="foo" />
<service id="another_alias_for_foo" alias="foo" public="false" />
<service id="factory_service" factory-method="getInstance" factory-service="baz_factory" />
+ <service id="lazy_deps_service">
+ <argument type="service" id="foo" lazy="true" />
+ <argument type="service" id="bar" />
+ </service>
</services>
</container>
View
2  src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml
@@ -24,3 +24,5 @@ services:
alias: foo
public: false
factory_service: { class: BazClass, factory_method: getInstance, factory_service: baz_factory }
+ lazy_deps_service:
+ arguments: ["@foo~", "@bar"]
View
4 src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
@@ -173,6 +173,10 @@ public function testLoadServices()
$this->assertEquals('getInstance', $services['factory_service']->getFactoryMethod());
$this->assertEquals('baz_factory', $services['factory_service']->getFactoryService());
+ $args = $services['lazy_deps_service']->getArguments();
+ $this->assertTrue($args[0]->isLazy());
+ $this->assertFalse($args[1]->isLazy());
+
$aliases = $container->getAliases();
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses <service> elements');
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
View
4 src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
@@ -128,6 +128,10 @@ public function testLoadServices()
$this->assertEquals(array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), $services['method_call2']->getMethodCalls(), '->load() parses the method_call tag');
$this->assertEquals('baz_factory', $services['factory_service']->getFactoryService());
+ $args = $services['lazy_deps_service']->getArguments();
+ $this->assertTrue($args[0]->isLazy());
+ $this->assertFalse($args[1]->isLazy());
+
$aliases = $container->getAliases();
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses aliases');
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
Something went wrong with that request. Please try again.