Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Merged
nicolas-grekas merged 1 commit intosymfony:7.3fromlyrixx:DIC-phpdoc
Apr 18, 2025

Conversation

@lyrixx
Copy link
Member

@lyrixxlyrixx commentedMar 31, 2025
edited
Loading

QA
Branch?7.3
Bug fix?no
New feature?yes
Deprecations?no
Issues
LicenseMIT

Seehttps://phpstan.org/r/a58736e6-f181-4bea-ad14-134f55229664

image

* @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))
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

VS code does not understand the psalm code, but this one is almost okay.

image

image

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?

@stof
Copy link
Member

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)

@wouterj
Copy link
Member

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.

chalasr reacted with thumbs up emoji

@lyrixx
Copy link
MemberAuthor

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)

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

stof commentedMar 31, 2025
edited
Loading

Indeed, but I thought it would be okay since it's only PHPDoc.

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)

If we add a sentence, to explain is not 100% accurate, would it be okay?

still -1 to me, because of the previous point.

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment
edited
Loading

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.

valtzu reacted with thumbs up emoji
@lyrixx
Copy link
MemberAuthor

okay, let's close it then

@lyrixxlyrixx closed thisMar 31, 2025
@lyrixx
Copy link
MemberAuthor

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

@lyrixxlyrixx reopened thisMar 31, 2025
@stof
Copy link
Member

Better than that: I'm fine if ppl that use a FQCN identifier without returning a compatible instance get a SA warning.

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

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.

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.

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

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

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.
Here, this is not a BC break: nothing changes. Also: no, we don't have to enforce phpdoc, so a runtime chek using eg service decoration wouldn't be required - because it's not our job to call something correctly. Types are just nicer if they help sort out not-working cases, but they're certainly not mandatory (we didn't have them before and we still delivered.)

So to me, DX clearly wins here.

@stof
Copy link
Member

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

nicolas-grekas commentedMar 31, 2025
edited
Loading

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

wouterj commentedMar 31, 2025
edited
Loading

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

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

We're not responsible for how people configure their containers, really.

@stof
Copy link
Member

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

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

@nicolas-grekas if the code is written against the common interface, things indeed work. But SA will tell you that the type you get isMyImplementation::class and won't complain if I pass it to a method that hasMyImplementation as parameter type instead of the common interface (which will then fail when running it as the type isMyDecorator).

When installing the phpstan-symfony extension, phpstan will infer the type of$container->get() calls based on the actual configuration of the container (it inspects the dumped XML file in the cache), which solves the use case of@lyrixx regarding testswithout introducing false positive for static analysis and without compromising the contract of the interface.

wouterj reacted with thumbs up emoji

@wouterj
Copy link
Member

wouterj commentedApr 5, 2025
edited
Loading

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.
I don't see a reason to merge a less ideal solution for a problem that is already fixed.

@nicolas-grekas
Copy link
Member

I don't know about the extension, but is the extension able to cope with scoped containers?
Because having the extension know about the main DI one doesn't really help DX, since this container shouldn't be used ANYWHERE (capital letter so that ppl that do so realize it's plain wrong 99,99% of the time.)

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?

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment
edited
Loading

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

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

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.

@chalasr
Copy link
Member

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

andrew-demb commentedApr 17, 2025
edited
Loading

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

@chalasr

benefit as this only matters in tests.

You missed Service Locator

@chalasr
Copy link
Member

@lyrixx I rather use the lowest possible abstraction which is the PSR interface, as in the docs and every locator of our codebase

wouterj, mtarld, and ro0NL reacted with thumbs up emoji

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment
edited
Loading

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

I don't thinkC|object will help much, as this would get normalized toobject

ro0NL reacted with heart emoji

@nicolas-grekas
Copy link
Member

nicolas-grekas commentedApr 18, 2025
edited
Loading

I don't think C|object will help much, as this would get normalized to object

I think C|object is the answer to@lyrixx previous comment:

What could solve my issue is something not interpreted by SA but only by IDE.

C|object does

ro0NL reacted with confused emoji

@lyrixx
Copy link
MemberAuthor

lyrixx commentedApr 18, 2025
edited
Loading

I updated the PR with@nicolas-grekas proposal. It seems good. Now I have autocomplete, and we don't messwith SA

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment

Choose a reason for hiding this comment

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

🚀

@nicolas-grekas
Copy link
Member

Thank you all for the discussion!

lyrixx reacted with thumbs up emojilyrixx and GromNaN reacted with heart emoji

@nicolas-grekas
Copy link
Member

Thank you@lyrixx.

@nicolas-grekasnicolas-grekas merged commitf30cc7b intosymfony:7.3Apr 18, 2025
6 of 11 checks passed
@lyrixxlyrixx deleted the DIC-phpdoc branchApril 20, 2025 08:34
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@stofstofstof left review comments

@fabpotfabpotfabpot approved these changes

@nicolas-grekasnicolas-grekasnicolas-grekas approved these changes

@GromNaNGromNaNGromNaN approved these changes

@chalasrchalasrchalasr approved these changes

Assignees

No one assigned

Projects

None yet

Milestone

7.3

Development

Successfully merging this pull request may close these issues.

10 participants

@lyrixx@stof@wouterj@nicolas-grekas@ro0NL@chalasr@andrew-demb@fabpot@GromNaN@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp