Skip to content

[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

Merged
merged 1 commit into from
Aug 31, 2017

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Aug 30, 2017

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

@stof
Copy link
Member

stof commented Aug 30, 2017

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.

@nicolas-grekas
Copy link
Member Author

Implementation pushed, not yet tested.

@nicolas-grekas nicolas-grekas force-pushed the di-ref-weak branch 5 times, most recently from 5b34f42 to 0908ea6 Compare August 31, 2017 09:07
@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 31, 2017

PR is now ready, with pretty good test coverage.
(failures are false positives)

Copy link
Contributor

@Simperfit Simperfit left a 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]);
Copy link
Contributor

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 ?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 31, 2017

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)

Copy link
Contributor

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) {
Copy link
Contributor

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...

Copy link
Member Author

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));
Copy link
Member

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

Copy link
Member Author

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);
Copy link
Member

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 ?

Copy link
Member Author

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 ?: '_'};
Copy link
Member

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 ?

Copy link
Member Author

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

@nicolas-grekas nicolas-grekas force-pushed the di-ref-weak branch 5 times, most recently from 5ad4e57 to 01137d4 Compare August 31, 2017 12:16
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()) {
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 findDefinition, to be compatible with references pointing to an alias

Copy link
Member Author

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*/)
Copy link
Member

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)

Copy link
Member Author

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;
}


Copy link
Member

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
Copy link
Member

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)

Copy link
Member Author

Choose a reason for hiding this comment

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

docblock fixed

@nicolas-grekas nicolas-grekas force-pushed the di-ref-weak branch 3 times, most recently from 5c0da4a to 474fb82 Compare August 31, 2017 12:44
* </ul>
*
* The container can have three possible behaviors when a service does not exist:
* The container can have three possible behaviors when a service
Copy link
Member

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

@nicolas-grekas nicolas-grekas force-pushed the di-ref-weak branch 2 times, most recently from fa97a16 to b584db1 Compare August 31, 2017 13:42
@nicolas-grekas
Copy link
Member Author

fabbot patch applied

@fabpot
Copy link
Member

fabpot commented Aug 31, 2017

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 0db3358 into symfony:3.4 Aug 31, 2017
fabpot added a commit that referenced this pull request Aug 31, 2017
…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
@nicolas-grekas nicolas-grekas deleted the di-ref-weak branch September 1, 2017 06:57
fabpot added a commit that referenced this pull request Sep 14, 2017
…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 was referenced Oct 18, 2017
fabpot added a commit that referenced this pull request Nov 26, 2017
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
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.

7 participants