Description
We encountered issue today (PHPStan 1.11.6 with bleeding edge + Symfony extension 1.4.5) that I consider violating the Liskov Substitution Principle. Consider simplified code:
services:
# Interface
Foo\ClientFactory: '@Foo\ApplicationClientFactory'
# Implementation
Foo\ApplicationClientFactory: ~
then, when we do something like:
/** @var \Foo\ClientFactory $factory */
$factory = $container->get(\Foo\ClientFactory::class);
we get an error:
PHPDoc tag @var with type Foo\ClientFactory is not subtype of type Foo\ApplicationClientFactory.
which does not make sense. We fetch ClientFactory
from DI container, it's aliased to ApplicationClientFactory
implementation that satisfies the interface, so of course interface is not a subtype, it's super type.
I don't want more specific type here, as the whole idea of interface is interchangeability. In the code, when I fetch ClientFactory
(interface) I don't care what implementation comes from DI container, I only want this particular contract, because I don't want to encounter problems when DI alias is changed to other implementation. In our case ApplicationClientFactory
has more methods that implemented ClientFactory
interface, I don't want developers to be able to use these methods because PHPStan allows it (as it knows the aliased service), rather the other way around - I would expect that static analysis reports usage of methods not defined in the interface.
I believe it's a bug and PHPStan should not narrow the type here. Similar here, I believe I should be allowed to restrict the contract to interface instead of relying on extended implementation.
FYI: we don't use @var
when we don't need, in this case it's only for IDE which does not have autocompletion for ClientFactory
fetched from the container (since get()
's return type is ?object
). We use dependency injection mostly, but unfortunately in legacy part of the code there is a lot of direct interaction with DI container.
Originally posted by @Wirone in #350 (comment)