-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE #24033
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
I think this is useful. It makes sense in several cases to iterate over initialized services for cleanup tasks. Having this kind of reference would allow to use IteratorArgument and private services instead of having to use public services and a list of ids as today. |
dacecf9
to
3506f34
Compare
Implementation pushed, not yet tested. |
5b34f42
to
0908ea6
Compare
PR is now ready, with pretty good test coverage. |
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.
Just some minor comments
@@ -304,9 +305,9 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE | |||
|
|||
try { | |||
if (isset($this->fileMap[$id])) { | |||
return $this->load($this->fileMap[$id]); | |||
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->load($this->fileMap[$id]); |
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't the value self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior
be stored in a variable ?
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.
it can, but it's better as is to me (wouldn't help readability, and the extra var is just overhead, so that's two reasons)
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.
Np then ;)
@@ -304,8 +304,12 @@ private function dumpValue($value) | |||
*/ | |||
private function getServiceCall($id, Reference $reference = null) | |||
{ | |||
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { | |||
return sprintf('@?%s', $id); | |||
if (null !== $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.
can't we do ?
if (null === $reference) {
return sprintf('@%s', $id);
}
switch...
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.
that would force duplicating the return sprintf('@%s', $id);
line.
$destId = (string) $value; | ||
|
||
if ($this->container->has($destId) && !$this->container->getDefinition($destId)->isShared()) { | ||
throw new InvalidArgumentException(sprintf('Invalid ignore-on-uninitialized reference found in service "%s": target service "%s" is non-shared.', $this->currentId, $destId)); |
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 would say is not shared
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.
updated
$conditions = array(); | ||
foreach ($services as $service) { | ||
foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { | ||
$conditions[] = sprintf("isset(\$this->services['%s'])", $service); |
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.
this needs to be updated based on service visibility when merging into master, right ? Does you testing covers this case for both private and public services ?
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.
done - and allowed me to spot an issue: now, the service graph is aware of those "weak" edges
}; | ||
$instance->iter = new RewindableGenerator(function () { | ||
if (isset($this->services['foo'])) { | ||
yield 'foo' => ${($_ = isset($this->services['foo']) ? $this->services['foo'] : null) && false ?: '_'}; |
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't we optimize this, as it repeats the wrapping condition ? Or is it not worth 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.
would add complexity, not worth it imho
5ad4e57
to
01137d4
Compare
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) { | ||
throw new ServiceNotFoundException($id, $this->currentId); | ||
} | ||
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() && $this->container->has($id = (string) $value) && !$this->container->getDefinition($id)->isShared()) { |
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.
should be findDefinition
, to be compatible with references pointing to an alias
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.
updated
*/ | ||
public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false*/) | ||
public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false*/) |
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.
should we tag this class as @final
to allow us to avoid having to do such magic for future changes to the container ? I'm not sure extending the ServiceReferenceGraph actually makes sense.
Tagging it as @internal
would be even more strict, but tools wanting to analyze the DIC to display debugging info may be a valid use case for using this class so I would not go that far (for instance, https://github.com/schmittjoh/JMSDebuggingBundle relies on it, even though I'm not sure it plays well with recent versions as I haven't used this bundle since years)
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 agree, @final
it is now
@@ -78,4 +81,15 @@ public function isLazy() | |||
{ | |||
return $this->lazy; | |||
} | |||
|
|||
|
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 have an extra line here. There should be only 1 empty line 😄 (see fabbot)
@@ -43,6 +43,7 @@ | |||
* * NULL_ON_INVALID_REFERENCE: Returns null | |||
* * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference | |||
* (for instance, ignore a setter if the service does not exist) | |||
* * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services |
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 would say for uninitialized services or invalid references
, to be clear that it also includes the behavior of IGNORE_ON_INVALID_REFERENCE
.
Btw, the phpdoc looks weird now, as the sentence before the bullet list says it impacts only cases where the service does not exist, which is not the case anymore. You may want to reword it a bit (btw, we should also rework the part about factory methods, as this talks about the deprecated API)
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.
docblock fixed
5c0da4a
to
474fb82
Compare
* </ul> | ||
* | ||
* The container can have three possible behaviors when a service does not exist: | ||
* The container can have three possible behaviors when a service |
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 4 behaviors now
fa97a16
to
b584db1
Compare
b584db1
to
0db3358
Compare
fabbot patch applied |
Thank you @nicolas-grekas. |
…EFERENCE (nicolas-grekas) This PR was merged into the 3.4 branch. Discussion ---------- [DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - As spotted in #23984 and #18244, supporting php-pm and the likes requires us to have a way to reset services, typically on `kernel.terminate`. Yet, only actually instantiated services need to be resetted. Knowing if a service is initialized or not is doable for public ones thanks to `Container::initialized()`. But for private ones, there is no way to achieve it. I propose to add a new way to reference services via this new const. When a reference has this behavior, two things happens: - at runtime, if the referenced service is not initialized, it behave the same as "ignore on invalid", pretending the service doesn't exist - at compile time, such references are weak, meaning their referenced services *can* be removed, but *cannot* be inlined Commits ------- 0db3358 [DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE
…e services (derrabus) This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle][HttpKernel] Add DI tag for resettable services | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23984 | License | MIT | Doc PR | TODO This PR uses #24033 to introduce a DI tag for resettable services. TODO after merge: * Add an interface, make the "method" attribute optional and enable autoconfiguration. * Consider adding a config option to enable/disable this feature. * Configure leaking services of the core bundles as resettable. Commits ------- d9a6b76 A DI tag for resettable services.
This PR was merged into the 3.4 branch. Discussion ---------- [DI] Remove unreachable code | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | #24033 added the ability to ignore uninitialized references, but the regex above the conditional would lead to an `InvalidArgumentException` being thrown. Commits ------- ced0857 Remove unreachable code
As spotted in #23984 and #18244, supporting php-pm and the likes requires us to have a way to reset services, typically on
kernel.terminate
. Yet, only actually instantiated services need to be resetted.Knowing if a service is initialized or not is doable for public ones thanks to
Container::initialized()
. But for private ones, there is no way to achieve it.I propose to add a new way to reference services via this new const. When a reference has this behavior, two things happens: