[DI] Implement PSR-11 #21265

Merged
merged 1 commit into from Feb 8, 2017
@greg0ire
Contributor
greg0ire commented Jan 12, 2017 edited

TODO:

  • wait for a stable version of the psr/container package;
  • deprecate instanciating ServiceNotFoundException directly, or using any of its methods directly; not relevant anymore
  • act on the outcome of php-fig/container#8 (solved in php-fig/container#9) ;
  • solve the mandatory NotFoundExceptionInterface on non-existing service
    problem (with a flag ?);
    non-issue, see comments below
  • provide meta-package psr/container-implementation if all problems can
    be solved.
Q A
Branch? master
New feature? yes
BC breaks? not at the moment
Tests pass? didn't pass before pushing, or even starting to code, will see Travis
License MIT
Doc PR TODO

This PR is a first attempt at implementing PSR-11, or at least trying to get closer to it.
Delegate lookup is optional, and thus not implemented for now.

@@ -21,7 +22,7 @@
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
-interface ContainerInterface
+interface ContainerInterface extends PsrContainerInterface
@greg0ire
greg0ire Jan 12, 2017 Contributor

I chose to have ContainerInterface extend PsrContainerInterface, so that any interface is marked as psr-compliant. Not sure it is the right thing to do though, because php cannot enforce all the contracts defined in the interface docs (like what exception to throw when). Tell me if you feel I should remove this and add implements ContainerInterface in the main implementation.

@nicolas-grekas
nicolas-grekas Jan 12, 2017 Member

If it works for PHP, fine.

@keradus
keradus Jan 13, 2017 Contributor

that basically means in whole framework you will not use Psr version, but Symfony version.
Then, one will be able to use concrete class that implements Symfony version as parameter for method expecting Psr version, but not the other way around. So, if I will have some package that follows Psr, I won't be able to use it directly in Symfony project, because everywhere the framework will require Symfony version instead

@greg0ire
greg0ire Jan 13, 2017 Contributor

everywhere the framework will require Symfony

Not sure I get what you mean, but IMO once this is merged, we should hunt for the Symfony version everywhere, see what calls are made there, and if we can replace use Psr version as type hinting there.

@nicolas-grekas
nicolas-grekas Jan 13, 2017 edited Member

@greg0ire without breaking BC, with the benefit of reducing the feature-set for the shake of PSR11 compliance? I doubt this is ever going to happen...

@greg0ire
greg0ire Jan 13, 2017 Contributor

I'm not talking about reducing the feature set, I'm saying, if we use the container in our internal class A, and we only call get and has on it, then we should change the type hinting. Not sure how this plays with extension, let me check

@greg0ire
greg0ire Jan 13, 2017 Contributor

Hmm… seems like it would indeed be a BC-break so… only for final classes I guess. https://3v4l.org/I8jO2

@greg0ire
greg0ire Jan 13, 2017 Contributor

Not sure why php complains about it though : every call to test you can make on C, you can make on D, right?

@jvasseur
jvasseur Jan 13, 2017 Contributor

Because PHP isn't aware of class hierarchy at compile time so it can't allow changing the type declaration.

@nicolas-grekas
nicolas-grekas Jan 13, 2017 Member

@jvasseur yes it does
the issue is that $foo instanceof A implies that calling $foo->test($anotherInstanceOfA) is allowed.
Since $anInstanceofB instanceof A, calling $anInstanceofB->test($anotherInstanceOfAButNotB) must thus be allowed.

@greg0ire
greg0ire Jan 13, 2017 Contributor

So… I found a bug in php?

@jvasseur
jvasseur Jan 13, 2017 edited Contributor

It's a know limitation of the PHP engine : https://bugs.php.net/bug.php?id=69612#1431343525

@jvasseur
jvasseur Jan 13, 2017 Contributor

But your example is wrong anyway, functions parameters could be contravariant (they are invariant in PHP) meaning they can accept a wider type. In your example they are covariant instead.

@greg0ire
greg0ire Jan 13, 2017 Contributor

Oh you're right, my logic was wrong indeed! Which means this would indeed be a BC-break, sorry I got confused.

/**
* Base InvalidArgumentException for Dependency Injection component.
*
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
-class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface, ContainerException
@greg0ire
greg0ire Jan 12, 2017 Contributor

EnvNotFound extends this exception, so no need to add implementation declaration in it.

@greg0ire
Contributor
greg0ire commented Jan 12, 2017 edited

About "the mandatory NotFoundExceptionInterface on non-existing service problem", throwing an exception on missing service id is optional in the current implementation while mandatory in the PSR. How should we solve this problem? I was thinking we could deprecate the second argument of get, but can we pretend this is a valid implementation if using get with a special argument makes it no longer compliant?

@@ -16,7 +16,8 @@
}
],
"require": {
- "php": ">=5.5.9"
+ "php": ">=5.5.9",
+ "psr/container": "^1.0@dev"
@greg0ire
greg0ire Jan 12, 2017 edited Contributor

See TODO list ;) , I'm not sure to be able to implement it perfectly, so first, let's get as close as we can without a BC-break.

@nicolas-grekas
nicolas-grekas Jan 12, 2017 Member

Still please add it.

@greg0ire
greg0ire Jan 12, 2017 Contributor

Fine πŸ‘

@GuilhemN
GuilhemN Jan 14, 2017 edited Contributor

Looks like it should also be added to the root composer.json.

@greg0ire
greg0ire Jan 14, 2017 Contributor

Uuuuh… it's already there, isn't it?

@GuilhemN
GuilhemN Jan 14, 2017 Contributor

Indeed... I missed it 😳

@greg0ire
greg0ire Jan 14, 2017 Contributor

Thanks for reviewing anyway :)

@nicolas-grekas

There is no issue with throwing : the PSR standardize only one argument, and says to throw. Fortunately that's the default behavior so anyone using the container in the PSR11 way will have it work as expected.
When the 2nd argument is used, we're not in PSR11 anymore, thus we can behave as we want.

@@ -21,7 +22,7 @@
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
-interface ContainerInterface
+interface ContainerInterface extends PsrContainerInterface
@nicolas-grekas
nicolas-grekas Jan 12, 2017 Member

If it works for PHP, fine.

@@ -16,7 +16,8 @@
}
],
"require": {
- "php": ">=5.5.9"
+ "php": ">=5.5.9",
+ "psr/container": "^1.0@dev"
@nicolas-grekas
nicolas-grekas Jan 12, 2017 Member

Still please add it.

@greg0ire
Contributor
greg0ire commented Jan 12, 2017 edited

When the 2nd argument is used, we're not in PSR11 anymore

Makes sense. We can't hurt people that don't know which implementation they are using, because if they don't they will probably not risk calling get with two arguments. So it's indeed a non-issue.

composer.json
@@ -20,6 +20,7 @@
"doctrine/common": "~2.4",
"twig/twig": "~1.28|~2.0",
"psr/cache": "~1.0",
+ "psr/container": "^1.0@dev",
@Soullivaneuh
Soullivaneuh Jan 12, 2017 Contributor

Just reminder comment: Remove @dev annotation when the package will be stable.

@fabpot
fabpot Feb 1, 2017 Member

Can we remove it now so that this PR is ready to be merged?

@greg0ire
greg0ire Feb 1, 2017 Contributor

fixed

@mnapoli
Contributor
mnapoli commented Jan 13, 2017

@greg0ire

Makes sense. We can't hurt people that don't know which implementation they are using, because if they don't they will probably not risk calling get with two arguments. So it's indeed a non-issue.

πŸ‘ the interface was designed to be compatible with Symfony's default behavior when called with the PSR signature.

@greg0ire
Contributor

πŸ‘ the interface was designed to be compatible with Symfony's default behavior when called with the PSR signature.

I did not read the underlying discussions for this spec but there definitely was a "well this is convenient" feeling when implementing it in Symfony πŸ˜„

@dunglas
Member
dunglas commented Jan 13, 2017

It looks only small changes are required, nice!

@greg0ire greg0ire changed the title from Implement PSR-11 to [DI] Implement PSR-11 Jan 13, 2017
@greg0ire
Contributor

@dunglas yup, that was a breeze :)

@nicolas-grekas
Member

RuntimeException also isn't it?

@nicolas-grekas
Member

Why not make ExceptionInterface extend Psr\Container\ContainerExceptionInterface?

@greg0ire
Contributor
greg0ire commented Jan 13, 2017 edited

Why not make ExceptionInterface extend Psr\Container\ContainerExceptionInterface?

Only exceptions thrown directly by the container are supposed to implement this, so I only applied it to exceptions listed here

This means this is an issue doesn't it ? Should I change this line so that the exception thrown here is wrapped into another?

@greg0ire
Contributor

RuntimeException also isn't it?

Can't find it in Container.php, so no.

@nicolas-grekas
Member

I suggest to play simpe here. We don't care to add the interface on all exceptions. This wouldn't hurt.

@greg0ire
Contributor

In fact, it can, see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php#L601

Yeah but :

Exceptions directly thrown by the container

What does "directly" mean here?

@nicolas-grekas
Member
nicolas-grekas commented Jan 13, 2017 edited

I don't know in fact, and that's a good question!
But throwing this exception in other cases is not going to make things not-PSR11 compliant. It just wouldn't be a normalized use case. Fine, nothing broken.
I showed you that in fact, via ContainerBuilder, there is a much greater variety of exceptions that can be thrown out there.

@greg0ire
Contributor
greg0ire commented Jan 13, 2017 edited

Very true, plus, less code, so… I'll change that. @mnapoli , can you advise about the meaning of "directly" here though? You know, for science?

@greg0ire
Contributor

There. The diff is tiny now :)

@greg0ire
Contributor

This means this is an issue doesn't it ? Should I change this line so that the exception thrown here is wrapped into another?

@nicolas-grekas what about this? Any thoughts?

@nicolas-grekas
Member
nicolas-grekas commented Jan 13, 2017 edited

Should I change this line so that the exception thrown here is wrapped into another?

maybe that's where "directly" applies, meaning "no" to you question.

Why did you remove applying the NotFoundExceptionInterface where applicable?

@greg0ire
Contributor

maybe that's where "directly" applies, meaning "no" to you question.

indeed πŸ‘

Why did you remove applying the NotFoundExceptionInterface where applicable?

looks like I was to hasty, sorry.

@greg0ire
Contributor

maybe that's where "directly" applies, meaning "no" to you question.

@mnapoli I'll make a PR to clarify that if you confirm

@greg0ire
Contributor

looks like I was to hasty, sorry.

@nicolas-grekas I think I got it right this time

@nicolas-grekas nicolas-grekas added this to the 3.x milestone Jan 13, 2017
@moufmouf

Hey @greg0ire ,

Looking at the current implementation you are proposing, regarding exceptions, there is a slight hickup.

The ServiceNotFoundException from Symfony is used both for saying that the user requested a given service and that a dependency is missing (i.e. one service is performing a lookup on another service that does not exists).

According to PSR-11, we should have 2 different exceptions for this (because only the first case should implement the ServiceNotFoundExceptionInterface).

The problem is hopefully easy enough to solve.
I fixed it in a fork from your fork (inception!), here: moufmouf@9960c36

Please merge that change if you find it appropriate.

To solve this issue and keep backward compatibility:

  • I created 2 new exceptions: RequestedServiceNotFoundException and MissingDependencyException.
  • Both exceptions extend the ServiceNotFoundException (this way, existing code catching this exception still works)
  • Only the RequestedServiceNotFoundException implements the ServiceNotFoundExceptionInterface

@nicolas-grekas I've seen your comments on the PHP-FIG mailing list and it's great to have Symfony feedback. We value your opinion and I have a very concrete question: do you think we should standardize a MissingDependencyExceptionInterface? Do you think PSR-11 would be a better standard if we do so?

// cc @mnapoli

@nicolas-grekas
Member

At first we think that implementing PSR-11 is trivial - then the exception handling part comes in and the diff becomes a lot less trivial.
Devil is in the detail (same lesson learned while implementing PSR-16 btw, nothing specific to PSR-11, nor to any PSR really).

do you think we should standardize a MissingDependencyExceptionInterface? Do you think PSR-11 would be a better standard if we do so?

I do :)

but there are remaining concerns in the recent comments.
Dunno how to be more constructive here sorry... Fortunately I don't vote on the FIG so it doesn't matter for you if I miss the point :)

@nicolas-grekas
Member

Maybe this, to be constructive: #20658
I think a PSR with what's currently in PSR-11 + a way for classes/objects to declare a set of services could maybe be worth it.

@greg0ire
Contributor

@moufmouf @nicolas-grekas I think there might be a contradiction here:

But throwing this exception in other cases is not going to make things not-PSR11 compliant. It just wouldn't be a normalized use case. Fine, nothing broken.

because only the first case should implement the ServiceNotFoundExceptionInterface

So which is it? Can exceptions that are not supposed to implement a psr interface implement it anyway or not?

@greg0ire
Contributor

After more thoughts @moufmouf @nicolas-grekas I think you're both right, I just couldn't understand why, but it might be because ContainerInterface is just a marker interface, while ServiceNotFoundExceptionInterface conveys meaning. Would that be why you did not add the suffix to the latter exception at first, but did add it to the former?

@greg0ire
Contributor

I fixed it in a fork from your fork (inception!), here: moufmouf@9960c36

I went all the way through with your inception concept and created a meta-PR : https://github.com/greg0ire/symfony/pull/1/files (will rebase once merged though)

@greg0ire
Contributor

Added @moufmouf 's commit to my PR

+ $msg = sprintf('You have requested a non-existent service "%s".', $id);
+
+ if ($alternatives) {
+ if (1 == count($alternatives)) {
@keradus
keradus Jan 15, 2017 Contributor

=== btw ;)

@greg0ire
greg0ire Jan 15, 2017 Contributor

count only returns integers, but ok…

+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class MissingDependencyException extends ServiceNotFoundException
@keradus
keradus Jan 15, 2017 Contributor

could we make them as final ?

@greg0ire
Contributor
greg0ire commented Jan 15, 2017 edited

@keradus : made exceptions introduced by @moufmouf final. Thanks for reviewing!

public function getId()
{
return $this->id;
}
-
- public function getSourceId()
@unkind
unkind Jan 16, 2017 Contributor

BC break?

@greg0ire
greg0ire Jan 16, 2017 Contributor

Classes that extend this exception will no longer get this method, so technically yeah. @moufmouf @nicolas-grekas , should I restore it, for BC's sake, or do we deem that "acceptable BC-break" ?

@nicolas-grekas
nicolas-grekas Jan 16, 2017 Member

changing a public interface is not an acceptable BC break - the class cannot be abstract for the same reason btw

@greg0ire
greg0ire Jan 16, 2017 Contributor

Ok let's fix this.

@greg0ire
greg0ire Jan 16, 2017 Contributor

What about the constructor? Removing it will not cause anything to crash, but then if it doesn't set sourceId, it's a BC-break, so in fact I'm starting to think I should restore the entirety of this class to its former self.

@moufmouf
moufmouf Jan 16, 2017

Good question. Are constructors of exceptions supposed to be part of public API? Is there a good reason for anyone to instantiate a ServiceNotFoundException except the symfony container itself?

@nicolas-grekas
nicolas-grekas Jan 16, 2017 edited Member

@moufmouf yes to both questions. At least that's the default answer, and if one thinks otherwise, one must demonstrates why at the technical level. Here, I doubt this is possible. So: yes and yes.

@unkind
unkind Jan 16, 2017 Contributor

Given ContainerInterface::get() declares this exception, one can decorate ContainerInterface and instantiate it.

@greg0ire
Contributor

given the recent discussion on the FIG about it, maybe you should just wait
because it might not be necessary to do this change at all in the end

β€” @nicolas-grekas , on the symfony dev slack channel

I'm putting this on hold until said discussion is resolved

@nicolas-grekas
Member

Now that php-fig/fig-standards#869 has been merged, I think we can remove the second commit here, isn't it?

@greg0ire
Contributor
greg0ire commented Jan 23, 2017 edited

Now that php-fig/fig-standards#869 has been merged

TL;DR There should be no distinction between missing dependency and service not found

@greg0ire
Contributor
greg0ire commented Jan 23, 2017 edited

I also should rework the first commit so that it does not reference this interface anymore, shouldn't I? oh no I misread

@nicolas-grekas
Member

Rebase needed + missing autowiring-type for service_container in src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

@greg0ire
Contributor
greg0ire commented Feb 1, 2017
composer.json
@@ -102,7 +103,8 @@
},
"provide": {
"psr/cache-implementation": "1.0",
- "psr/simple-cache-implementation": "1.0"
+ "psr/simple-cache-implementation": "1.0",
+ "psr/container-implementation": "1.0"
@nicolas-grekas
nicolas-grekas Feb 1, 2017 Member

alpha order

@greg0ire
greg0ire Feb 1, 2017 Contributor

I must be tired :P

@greg0ire
greg0ire Feb 1, 2017 Contributor

fixed

@greg0ire
Contributor
greg0ire commented Feb 1, 2017 edited

Travis passes although there is no stable release yet… I don't get it. It there minimum-stability: dev somewhere?

EDIT: answering my own question, it's right in the composer.json

@nicolas-grekas
Member

another rebase please? should be the last :)

@greg0ire
Contributor
greg0ire commented Feb 2, 2017

Oh wow bye bye autowiring huh? I noticed the service was no longer synthetic too, is that normal? Not sure I got it right…

@nicolas-grekas
Member

You got it right. See #21494 for more info about this change.
The service_container is already declared implicitly, it was here only to add autowiring-types, but these are gone now.

@@ -40,6 +40,7 @@
<argument type="collection" />
</service>
+ <service id="Prs\Container\ContainerInterface" alias="service_container" public="false" />
@greg0ire
greg0ire Feb 2, 2017 edited Contributor

Sorry, I retried with auto-completion this time… I was barely awoken

@greg0ire greg0ire Implement PSR-11
Delegate lookup is optional and thus, not implemented.
bde0efd
@chalasr
Contributor
chalasr commented Feb 8, 2017

Needs review?

@greg0ire
Contributor
greg0ire commented Feb 8, 2017

Needs review?

The more eyeballs the better, not sure you are going to find much to say now though.

I think the Needs work label could maybe be removed, this is more blocked, because we are waiting for a release of the psr dependency.

@nicolas-grekas

πŸ‘

@dunglas
Member
dunglas commented Feb 8, 2017

πŸ‘

@dunglas
dunglas approved these changes Feb 8, 2017 View changes
@javiereguiluz

πŸ‘

@fabpot
Member
fabpot commented Feb 8, 2017

Thank you @greg0ire.

@fabpot fabpot merged commit bde0efd into symfony:master Feb 8, 2017

3 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
fabbot.io Your code looks good.
Details
@fabpot fabpot added a commit that referenced this pull request Feb 8, 2017
@fabpot fabpot feature #21265 [DI] Implement PSR-11 (greg0ire)
This PR was merged into the 3.3-dev branch.

Discussion
----------

[DI] Implement PSR-11

TODO:

- [x] wait for a stable version of the psr/container package;
- [x] ~~deprecate instanciating ServiceNotFoundException directly, or using any of its methods directly;~~ not relevant anymore
- [x] act on the outcome of php-fig/container#8 (solved in php-fig/container#9) ;
- [x] ~~solve the mandatory NotFoundExceptionInterface on non-existing service
problem (with a flag ?);~~ non-issue, see comments below
- [x] provide meta-package psr/container-implementation if all problems can
be solved.

| Q             | A
| ------------- | ---
| Branch?       | master
| New feature?  | yes
| BC breaks?    | not at the moment
| Tests pass?   | didn't pass before pushing, or even starting to code, will see Travis
| License       | MIT
| Doc PR        | TODO

This PR is a first attempt at implementing PSR-11, or at least trying to get closer to it.
Delegate lookup is optional, and thus not implemented for now.

Commits
-------

bde0efd Implement PSR-11
bcd897c
@greg0ire greg0ire deleted the greg0ire:implement_psr_11 branch Feb 8, 2017
@greg0ire
Contributor
greg0ire commented Feb 8, 2017

Thanks for merging!

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