Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[DependencyInjection] Add better return type onContainerInterface::get()#60096
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
| * @psalm-return (B is self::EXCEPTION_ON_INVALID_REFERENCE|self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE ? object : object|null) | ||
| * @psalm-return ($id is class-string<A> ? (B is self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE|self::EXCEPTION_ON_INVALID_REFERENCE ? A : A|null) : (B is self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE|self::EXCEPTION_ON_INVALID_REFERENCE ? object : object|null)) | ||
| * | ||
| * @return ($id is class-string<A> ? (B is 0|1 ? A : A|null) : (B is 0|1 ? object : object|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.
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.
did you try with phpstan-return?
Uh oh!
There was an error while loading.Please reload this page.
stof commentedMar 31, 2025
There is strictly no guarantee in the ContainerInterface that a service class is a subtype of the object id, even when the object id is a class-string. This PR adds this in the contract of the interface, which is a BC break to me (btw, it might force us to forbid some kind of service decoration to ensure this is true) |
Uh oh!
There was an error while loading.Please reload this page.
wouterj commentedMar 31, 2025
I agree with@stof here. We decided against using PHPdoc types that do not represent the 100% use-case, even if it improves the experience for 95% There is no guarantee that there is a direct relation between service ID and object type, even though it's a common convention that there is. So 👎 from me. |
lyrixx commentedMar 31, 2025
Indeed, but I thought it would be okay since it's only PHPDoc. But since it increase the DX a lot in test env, I thought it would be okay. If we add a sentence, to explain is not 100% accurate, would it be okay? |
stof commentedMar 31, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Our phpdoc in our interfaces is considered as part of the defined contract (it is taken into account when deciding whether the change we do is a BC break or no)
still -1 to me, because of the previous point. |
nicolas-grekas left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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'm sympathetic to the change.
The DX improvement is way more important to me than covering a meta programming edge case. Better than that: I'm fine if ppl that use a FQCN identifier without returning a compatible instance get a SA warning. They'd rely on confusing conventions.
No BC break here, this has no runtime effect.
lyrixx commentedMar 31, 2025
okay, let's close it then |
lyrixx commentedMar 31, 2025
Oh, there was a race condition here. I close the PR at the same time of last comment from@nicolas-grekas. Let's re-open |
stof commentedMar 31, 2025
They would not. they might get a runtime error because the runtime type does not match what the SA thinks the type was. Static analysis won't report errors on the service definition file if not respecting that rule (as it does not even analyze those XML/Yaml files) and the original service definition might even respect the rule without the final one respecting it. |
nicolas-grekas commentedMar 31, 2025
I don't think your interpretation of the BC policy is the one we're really sticking to. At least I don't agree with this argument when considering the current proposal. To me its totally fine and desired I'd say.
This is reverse thinking: the current declaration doesn't provide anything like that either, so the new one won't provide more guarantees. The DX improvement is obvious, the SA downside is none in practice. |
stof commentedMar 31, 2025
The new phpdoc explicitly says "if the id looks like a class-string, the method MUST return an object of that type (or null)". That's definitely a stricter contract for the interface than saying "the method MUST return an object (or null)". And phpdoc types have always been part of our BC policy (otherwise, adding native types would have been a BC break forcallers of our methods due to narrowing the parameter types) |
nicolas-grekas commentedMar 31, 2025
The BC policy is designed to allow people to upgrade seamlessly. Seamlessly "means without us breaking their app". Yes we consider types in phpdocs, as guides toward what should be supported. We also change them at will when needed, provided this doesn't create any execution-time side effect - which is the core criteria when BC is concerned. So to me, DX clearly wins here. |
stof commentedMar 31, 2025
@nicolas-grekas interface implementations have to respect the contract defined in the interface. That's where the BC break is when making the interface stricter (and with this PR, we would end up allowing to instantiate a Container that does not respect ContainerInterface depending on how services are configured) |
nicolas-grekas commentedMar 31, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
The "allowing" part is not our business, same as one can wire a logger into a service argument that needs a serializer: "PHP" will throw, not "us". People are free to run whatever works for them. Declaring the intent is enough on our side, and will always be. Then no, no BC break here, because no exec-time side effect... |
wouterj commentedMar 31, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Implementations of this interface will get static analysis errors if they don't guarantee a correct return object type if a class FQCN is passed. That's a regression from the current state. Still, I don't think we consider static analysis errors a BC break (otherwise, any additional PHPdoc type is a BC break as it could lead to new static analysis errors). However, this does change how we treat PHPdoc types. If we think this is OK, there are more types that we welcome from the phpstan/psalm stubs. We rejected these in the past, as they didn't represent the 100% truth (#47016). E.g. the stubbed form types which are a bigger elephpant in the room. |
stof commentedMar 31, 2025
@nicolas-grekas the big difference is that in this case, the class that won't respect its interface isour class. So the faulty code would be ours. |
nicolas-grekas commentedMar 31, 2025
We're not responsible for how people configure their containers, really. |
stof commentedMar 31, 2025
Adding a decorator on a service that was registered using its class name as id (the default convention) is enough to make the service invalid regarding this new rule (the dev intent is probably to decorate based on the interface of the class rather than the class itself). And this does not require weird wiring of the container, just using one of its flagship features. |
nicolas-grekas commentedMar 31, 2025
Sure, but still: the decorator has to act like the real objects, so the API will still be the same. SA tools won't be able to notice anyway, too many indirections. And if they do, ppl can decorate the common interface that the decorated and decorator must share. |
stof commentedMar 31, 2025
@nicolas-grekas if the code is written against the common interface, things indeed work. But SA will tell you that the type you get is When installing the phpstan-symfony extension, phpstan will infer the type of |
wouterj commentedApr 5, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
That's a very good point. All static analysers are using our XML dump of the container, which we make specifically for this use-case: mapping magic service ID strings to PHP object information. |
nicolas-grekas commentedApr 7, 2025
I don't know about the extension, but is the extension able to cope with scoped containers? On the other end, scoped containers are a thing ppl can use at will. Yet how are they supported by the extension, DX wise? @lyrixx maybe you'd have some hints on these questions? |
nicolas-grekas left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
👍 on my side. Thanks for the discussion, it's insightful.
My reasoning:
- this improves DX for sure
- the potential inaccuracy when decorators are used happens only when people don't use IoC, so that's their problem and there is a clear path forward: SOLID principles FTW
- the potential inaccuracy when decorators are used doesn't impact either DX nor static analyzers since one should still use the returned object as if it were an instance of FQCN
- for static analyzers that can properly understand how a container is configured (I've yet to see this happen), they might report issues. Yet the answer is the same as above: use IoC to fix the warning.
DX should be first class here IMHO.
stof commentedApr 17, 2025
@nicolas-grekas you seem to say that using a concrete class name as service id breaks IOC. but that's the default behavior of Symfony. |
nicolas-grekas commentedApr 17, 2025
When using AutowireLocator you mean, sure. Still, it's a matter of vision. Your reasoning makes sense in a world where types are reified. Mine works in an erased types world. I prefer erased types: they're good enough to solve the purpose of types in a language like PHP. |
Uh oh!
There was an error while loading.Please reload this page.
chalasr commentedApr 17, 2025
I'm 👎 on this for the same reasons as stof and wouter (3 downvotes). This takes a too big shortcut for a too small benefit as this only matters in tests. Not worth the hack/wrong assumption IMHO |
andrew-demb commentedApr 17, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
What do you think about changing phpdoc to rely on interfaces then? https://psalm.dev/r/2c3a6cac5a I don't know whether IDE will handle this properly, and why for now Psalm resolves class-string as interface-string too, but this will be the most safe approarch in my opinion <?phpinterface Container{/** * @template T * @template V * * @param T $id * * @return T is interface-string<V> ? V : mixed */publicfunctionget(string$id):mixed; }$container =container();/** @psalm-trace $interfaceInstance */$interfaceInstance =$container->get(\ArrayAccess::class);// expected - `\ArrayAccess`/** @psalm-trace $classInstance */$classInstance =$container->get(\stdClass::class);// expected - `mixed`functioncontainer():Container{thrownew \InvalidArgumentException('Not necessary'); } |
lyrixx commentedApr 17, 2025
You missed Service Locator |
chalasr commentedApr 17, 2025
@lyrixx I rather use the lowest possible abstraction which is the PSR interface, as in the docs and every locator of our codebase |
nicolas-grekas left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
What about this? Would that improve the DX? From a type PoV, it's accurate.
@return ($id is class-string<C> ? (B is 0|1 ? C|object : C|object|null) : (B is 0|1 ? object : object|null))
stof commentedApr 18, 2025
I don't think |
nicolas-grekas commentedApr 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I think C|object is the answer to@lyrixx previous comment:
C|object does |
lyrixx commentedApr 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I updated the PR with@nicolas-grekas proposal. It seems good. Now I have autocomplete, and we don't messwith SA |
nicolas-grekas left a comment
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.
🚀
nicolas-grekas commentedApr 18, 2025
Thank you all for the discussion! |
nicolas-grekas commentedApr 18, 2025
Thank you@lyrixx. |
f30cc7b intosymfony:7.3Uh oh!
There was an error while loading.Please reload this page.


Uh oh!
There was an error while loading.Please reload this page.
Seehttps://phpstan.org/r/a58736e6-f181-4bea-ad14-134f55229664