Skip to content

[DependencyInjection] Implement lazy collection type using generators #20907

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

Merged
merged 2 commits into from
Jan 6, 2017

Conversation

tgalopin
Copy link
Contributor

@tgalopin tgalopin commented Dec 13, 2016

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? -
Fixed tickets #20875
License MIT
Doc PR -

Following the RFC #20875, this PR aims to implement lazy services collections using generators internally.

For now, I suggest to support only XML and PHP definitions. Supporting YAML means adding another convention to the language and this is not the aim of this PR.

  • value object holding the semantic (IteratorArgument)
  • iterator dumping
  • lazyness awareness in CheckCircularReferencesPass & GraphvizDumper
  • rewindable iterators
  • *_ON_INVALID_REFERENCE behavior
  • ContainerBuilder::createService
  • ArgumentInterface handling in compiler passes

@tgalopin tgalopin force-pushed the lazy-collections branch 2 times, most recently from 60fef16 to 19a3df9 Compare December 13, 2016 17:52
@nicolas-grekas nicolas-grekas changed the title [WIP][DependencyInjection] Implement lazy collection type using generators [DependencyInjection] Implement lazy collection type using generators Dec 13, 2016
@tgalopin tgalopin force-pushed the lazy-collections branch 2 times, most recently from d85d9a8 to bf39a70 Compare December 14, 2016 12:54
@tgalopin
Copy link
Contributor Author

IMO this is ready for review. The Travis failure is not llinked to this PR.

@tgalopin
Copy link
Contributor Author

I fixed some issues found by fabbot.io.

@tgalopin tgalopin force-pushed the lazy-collections branch 2 times, most recently from a19f2ca to 6aa3d65 Compare December 14, 2016 13:30
@@ -632,7 +633,7 @@ private function addService($id, Definition $definition)
}

if ($definition->isAutowired()) {
$doc = <<<EOF
$doc = <<<'EOF'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be reverted. I know that it comes from php-cs-fixer, but we should instead disable this rule in .php.cs.dist

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot is it intended, even in php cs fixer, or is it a bug ? I don't see the value of quoting the heredoc

@nicolas-grekas
Copy link
Member

We miss an equivalent to #19902, which means we miss a way to walk through the values in IteratorArgument in compiler passes.
We need a new ArgumentInterface to allow compiler passes to do that. IteratorArgument would be the first to implement it, but more should come soon (see my other RFCs).

@tgalopin tgalopin force-pushed the lazy-collections branch 2 times, most recently from 93da7d3 to 8985dd8 Compare December 14, 2016 17:59
@@ -65,4 +68,14 @@ public function getDestNode()
{
return $this->destNode;
}

/**
* Returns true if the edge is lazy, meaning it's a dependency not requiring direct instanciation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: instanciation should be instantiation

@jvasseur
Copy link
Contributor

jvasseur commented Dec 15, 2016

Using generators has one downside, they aren't rewindable meaning services using it will only be able to iterate on them once.

If we keep it like this developers will have store the result of the iteration into another variable. This means we will lose in terms of DX and that we will lose the possibility to stop iterating mid-collection (event with stoped propagation, stopping at the first service that accept the input, etc...) losing the laziness of only instantiating the first services.

A solution would be to wrap the generator into a class like this one https://github.com/nikic/iter/blob/master/src/iter.rewindable.php#L85-L136 to create rewindable generators.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Dec 15, 2016

@jvasseur good point! We just don't need so much code to create a rewindable generator in our case.

Here is what I came up with:

/**
 * @internal
 */
class RewindableGenerator implements \IteratorAggregate
{
    private $generator;

    public function __construct(callable $generator)
    {
        $this->generator = $generator;
    }

    public function getIterator()
    {
        $g = $this->generator;

        return $g();
    }
}

namespace Symfony\Component\DependencyInjection\Argument;

/**
* Represents a complex argument containg nested values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo containg => containing

public function getValues();

/**
* @param array $values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless

@@ -91,19 +92,25 @@ public function process(ContainerBuilder $container)
* Processes service definitions for arguments to find relationships for the service graph.
*
* @param array $arguments An array of Reference or Definition objects relating to service definitions
* @param boolean $lazy Whether the references nested in the arguments should be considered lazy or not
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool (same below) + alignement

@@ -95,7 +96,14 @@ private function addNodes()
foreach ($this->nodes as $id => $node) {
$aliases = $this->getAliases($id);

$code .= sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id.($aliases ? ' ('.implode(', ', $aliases).')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes']));
$code .= sprintf(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about the cs change, we have a preference for long lines (and eases review)

@nicolas-grekas nicolas-grekas added this to the 3.3 milestone Dec 19, 2016
foreach ($lazyContext->lazyValues as $k => $v) {
++$i;

if ($i === 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Yoda style.

continue;
}

if ($i === 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (Yoda).

continue;
}

if ($i === 2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (Yoda).

continue;
}

if ($i === 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (Yoda).

continue;
}

if ($i === 4) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (Yoda).

@dunglas
Copy link
Member

dunglas commented Dec 20, 2016

👍 great!

@fabpot
Copy link
Member

fabpot commented Jan 5, 2017

Great job so far!

Some remarks:

  • We need to define the format for YAML as it should definitely be part of this PR;
  • This feature should be added to the CHANGELOG file.

$code[] = 'new RewindableGenerator(function() {';
foreach ($value->getValues() as $k => $v) {
$v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)));
foreach (explode("\n", $v) as $v) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved to a private method for better clarity, indentCode.

@nicolas-grekas
Copy link
Member

Yaml done, eg:

my_service:
    class: FooBar
    arguments:
        - '@service1'
        - =iterator:
            - '@service2'
            - '@service3'

use Symfony\Component\Yaml\Dumper as YmlDumper;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be reverted

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted

@fabpot
Copy link
Member

fabpot commented Jan 5, 2017

👍

@fabpot
Copy link
Member

fabpot commented Jan 6, 2017

Thank you @tgalopin.

@fabpot fabpot merged commit 1dbf52a into symfony:master Jan 6, 2017
fabpot added a commit that referenced this pull request Jan 6, 2017
…sing generators (tgalopin, nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DependencyInjection] Implement lazy collection type using generators

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | -
| Fixed tickets | #20875
| License       | MIT
| Doc PR        | -

Following the RFC #20875, this PR aims to implement lazy services collections using generators internally.

For now, I suggest to support only XML and PHP definitions. Supporting YAML means adding another convention to the language and this is not the aim of this PR.

- [x] value object holding the semantic (IteratorArgument)
- [x] iterator dumping
- [x] lazyness awareness in CheckCircularReferencesPass & GraphvizDumper
- [x] rewindable iterators
- [x] `*_ON_INVALID_REFERENCE` behavior
- [x] ContainerBuilder::createService
- [x] ArgumentInterface handling in compiler passes

Commits
-------

1dbf52a [DI] Add "=iterator" arguments to Yaml loader
5313943 [DependencyInjection] Implement lazy collection type using generators
symfony-splitter pushed a commit to symfony/dependency-injection that referenced this pull request Feb 10, 2017
This PR was squashed before being merged into the 3.3-dev branch (closes #21194).

Discussion
----------

[Yaml] Add tags support

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | symfony/symfony#21185
| License       | MIT
| Doc PR        |

This PR adds custom tags support to the Yaml component.
Symfony tags (`!!binary`, `!str`, etc.) are still managed in the parser to have a lighter diff but we'll be able to convert them later if we want to.

The primary addition of this PR is the `TagInterface`:
```php
interface TagInterface
{
    public function construct(mixed $value): mixed;
}
```

It can be used to register custom tags. An example that could be used to convert [the syntax `=iterator`](symfony/symfony#20907 (comment)) to a tag:
```php
final class IteratorTag implements TagInterface
{
    public function construct(mixed $value): mixed
    {
        return new IteratorArgument($value);
    }
}

$parser = new Parser(['iterator' => new IteratorTag()]);
```

If you think this is too complex, @nicolas-grekas [proposed an alternative](symfony/symfony#21185 (comment)) to my proposal externalizing this support by introducing a new class `TaggedValue`.

Commits
-------

4744107 [Yaml] Add tags support
nicolas-grekas added a commit that referenced this pull request Feb 10, 2017
This PR was squashed before being merged into the 3.3-dev branch (closes #21194).

Discussion
----------

[Yaml] Add tags support

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #21185
| License       | MIT
| Doc PR        |

This PR adds custom tags support to the Yaml component.
Symfony tags (`!!binary`, `!str`, etc.) are still managed in the parser to have a lighter diff but we'll be able to convert them later if we want to.

The primary addition of this PR is the `TagInterface`:
```php
interface TagInterface
{
    public function construct(mixed $value): mixed;
}
```

It can be used to register custom tags. An example that could be used to convert [the syntax `=iterator`](#20907 (comment)) to a tag:
```php
final class IteratorTag implements TagInterface
{
    public function construct(mixed $value): mixed
    {
        return new IteratorArgument($value);
    }
}

$parser = new Parser(['iterator' => new IteratorTag()]);
```

If you think this is too complex, @nicolas-grekas [proposed an alternative](#21185 (comment)) to my proposal externalizing this support by introducing a new class `TaggedValue`.

Commits
-------

4744107 [Yaml] Add tags support
@fabpot fabpot mentioned this pull request May 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants