-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Allow to count on lazy collection arguments #21455
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
[DI] Allow to count on lazy collection arguments #21455
Conversation
* @param callable $generator | ||
* @param int|null $count If provided, used as the returned value for count. iterator_to_array will be used otherwise. | ||
*/ | ||
public function __construct(callable $generator, $count = 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.
As this class is internal and used only for this purpose, I think I can drop the $count = null
behavior, though.
public function count() | ||
{ | ||
if (null === $this->count) { | ||
return count(iterator_to_array($this->getIterator())); |
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.
iterator_count($this->getIterator())
instead?
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.
👍 (But I'm sure we don't keep this behavior anyway ^^' (#21455 (comment)))
I would not add the |
@chalasr: calling For instance, in #21450, calling |
* @param callable $generator | ||
* @param int|null $count If provided, used as the returned value for count. iterator_to_array will be used otherwise. | ||
*/ | ||
public function __construct(callable $generator, $count = 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.
Aren't we always able to have the count? If yes, I'd make count mandatory and simplify the count()
method.
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, it confirms #21455 (comment) (I'll remove this)
Wait, we can't! There maybe be conditional "yield", when |
Let's close and tweak the error message instead sorry for the bad suggestion. |
@nicolas-grekas : But this case can be properly handled, right? See https://github.com/ogizanagi/symfony/blob/3dfcbe10ac3aab14922b92afb0818d35cfa8ba8c/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php#L344 |
That's wise :) |
Ready to review |
LGTM 👍 |
@@ -966,6 +966,18 @@ public function resolveServices($value) | |||
} | |||
} elseif ($value instanceof IteratorArgument) { | |||
$parameterBag = $this->getParameterBag(); | |||
|
|||
$count = 0; | |||
foreach ($value->getValues() as $k => $v) { |
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.
can be wrapped in a closure as second arg below
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.
Indeed 👍
$code = array(); | ||
$code[] = 'new RewindableGenerator(function() {'; | ||
foreach ($value->getValues() as $k => $v) { |
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.
$v is nice, makes the diff easier to review also
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.
$v
is nice, but a need to exploit $value
again for conditionals count. $v
is overwritten by:
$v = $this->wrapServiceConditionals($value, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($value,
Can use something else for this though.
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.
See cfdc528
foreach (explode("\n", $v) as $v) { | ||
if ($v) { | ||
$code[] = ' '.$v; | ||
} | ||
} | ||
|
||
($c = $this->getServiceConditionals($value)) ? $operands[] = "(int) ($c)" : ++$operands[0]; |
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.
when there are several conditionals, this will always evaluate to 1, isn't it?
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.
If there are several conditions, we'll get something like:
(int) ($this->has('foo') && $this->has('other'))
for a single argument, so should be ok, right? (Actually I don't even know why you would have several conditions for a single service. In case of a collection of service all being ignored on invalid reference?)
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.
there are two cases:
- two optional lines in the list of values: these should count from zero to two condionally
- a single line with two contitionals inside (eg
- [ @?foo, @?bar ]
: these count for zero or one only of course
ad both can be mixed, this one counting from zero to two also, (one per line):
- [ '@?foo1', '@?bar1' ]
- [ '@?foo2', '@?bar2' ]
} | ||
} | ||
|
||
++$count; |
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.
missing return statement?
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.
Grumpf. Good catch. 😅
I need to add a test case.
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.
} | ||
} | ||
|
||
($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; |
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.
can you move this line before calling wrapServiceConditionals and remove the need for the $y
variable?
@@ -1387,17 +1403,26 @@ private function dumpValue($value, $interpolate = true) | |||
|
|||
return sprintf('array(%s)', implode(', ', $code)); | |||
} elseif ($value instanceof IteratorArgument) { | |||
$countCode = array(); | |||
$countCode[] = 'function() {'; |
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.
There should be a space between function
and ()
to be valid with PSR-2
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 did first. But any function generated in the dumped container by the PhpDumper gives function()
currently.
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.
then you can fix it in this PR also :)
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 rather do a PR on 2.7 to fix other occurrences (if there is in this branch, didn't check yet), don't you think?
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.
Ok. There isn't any (appart from 3.3). I'll do it here 😆
$code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); | ||
|
||
return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code); | ||
return sprintf('%s', implode(' && ', $conditions)); |
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.
return implode(' && ', $conditions);
?
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.
Ah ah. Sure 😆
Thanks
👍 |
|
||
public function count() | ||
{ | ||
if (is_callable($count = $this->count)) { |
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 need the local $count
variable here?
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 need it as calling $this->count()
would fail below, but could be declared inside the if indeed
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 thinking about this again I would keep it as is.
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.
👍
foreach ($value->getValues() as $k => $v) { | ||
($c = $this->getServiceConditionals($v)) ? $operands[] = "(int)($c)" : ++$operands[0]; |
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.
Shouldn't there be a space after the int
type cast?
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're right. It's in the symfony rules. I don't know why I reverted this.
👍 |
Thank you @ogizanagi. |
…anagi) This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Allow to count on lazy collection arguments | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #21450 (comment) | License | MIT | Doc PR | todo (with symfony/symfony-docs#7336) When using the new iterator feature of the DI component to lazy load collection, we always know the number of arguments in the collection (only the invalidBehavior set to `IGNORE_ON_INVALID_REFERENCE` may change this number). So we are able to generate and use a `RewindableGenerator` implementing `\Countable` by computing this value ahead. So, in a service accepting `array|iterable`, like in the `GuardAuthenticationListener` (see #21450): ```php class GuardAuthenticationListener implements ListenerInterface { private $guardAuthenticators; /** * @param iterable|GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider * @param LoggerInterface $logger A LoggerInterface instance */ public function __construct($guardAuthenticators, LoggerInterface $logger = null) { // ... } public function handle(GetResponseEvent $event) { if (null !== $this->logger) { $context = array() if (is_array($this->guardAuthenticators) || $this->guardAuthenticators instanceof \Countable) { $context['authenticators'] = count($this->guardAuthenticators); } $this->logger->debug('Checking for guard authentication credentials.', $context); } // ... } } ``` we still keep the ability to call count without loosing the lazy load benefits. Commits ------- f23e460 [DI] Allow to count on lazy collection arguments
When using the new iterator feature of the DI component to lazy load collection, we always know the number of arguments in the collection (only the invalidBehavior set to
IGNORE_ON_INVALID_REFERENCE
may change this number). So we are able to generate and use aRewindableGenerator
implementing\Countable
by computing this value ahead.So, in a service accepting
array|iterable
, like in theGuardAuthenticationListener
(see #21450):we still keep the ability to call count without loosing the lazy load benefits.