diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 0164d512..086fcea0 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -211,6 +211,11 @@ + + + + + diff --git a/src/PluginManagerInterface.php b/src/PluginManagerInterface.php index 483dece7..ed04d137 100644 --- a/src/PluginManagerInterface.php +++ b/src/PluginManagerInterface.php @@ -29,7 +29,7 @@ public function validate(mixed $instance): void; /** * @template TRequestedInstance extends InstanceType * @psalm-param class-string|string $id Service name of plugin to retrieve. - * @psalm-return ($id is class-string ? TRequestedInstance : InstanceType) + * @psalm-return ($id is class-string ? TRequestedInstance : InstanceType) * @throws Exception\ServiceNotFoundException If the manager does not have * a service definition for the instance, and the service is not * auto-invokable. @@ -43,7 +43,7 @@ public function get(string $id): mixed; * * @template TRequestedInstance extends InstanceType * @psalm-param string|class-string $name - * @psalm-return ($name is class-string ? TRequestedInstance : InstanceType) + * @psalm-return ($name is class-string ? TRequestedInstance : InstanceType) * @throws Exception\ServiceNotFoundException If no factory/abstract * factory could be found to create the instance. * @throws Exception\ServiceNotCreatedException If factory/delegator fails diff --git a/src/ServiceLocatorInterface.php b/src/ServiceLocatorInterface.php index a7ecb161..b4e91977 100644 --- a/src/ServiceLocatorInterface.php +++ b/src/ServiceLocatorInterface.php @@ -8,6 +8,7 @@ use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; /** * Interface for service locator @@ -15,7 +16,7 @@ interface ServiceLocatorInterface extends ContainerInterface { /** - * Build a service by its name, using optional options (such services are NEVER cached). + * Builds a service by its name, using optional options (such services are NEVER cached). * * @template T of object * @param string|class-string $name @@ -27,4 +28,15 @@ interface ServiceLocatorInterface extends ContainerInterface * @throws ContainerExceptionInterface If any other error occurs. */ public function build(string $name, ?array $options = null): mixed; + + /** + * Finds an entry of the container by its identifier and returns it. + * + * @template T of object + * @param string|class-string $id + * @psalm-return ($id is class-string ? T : mixed) + * @throws ContainerExceptionInterface Error while retrieving the entry. + * @throws NotFoundExceptionInterface No entry was found for **this** identifier. + */ + public function get(string $id); } diff --git a/src/ServiceManager.php b/src/ServiceManager.php index 1d0b2b63..f97480ff 100644 --- a/src/ServiceManager.php +++ b/src/ServiceManager.php @@ -202,6 +202,7 @@ public function get(string $id): mixed // We start by checking if we have cached the requested service; // this is the fastest method. if (isset($this->services[$id])) { + /** @psalm-suppress MixedReturnStatement Yes indeed, service managers can return mixed. */ return $this->services[$id]; } @@ -217,6 +218,8 @@ public function get(string $id): mixed if ($sharedService) { $this->services[$id] = $service; } + + /** @psalm-suppress MixedReturnStatement Yes indeed, service managers can return mixed. */ return $service; } @@ -234,6 +237,8 @@ public function get(string $id): mixed // If the alias is configured as a shared service, we are done. if ($sharedAlias) { $this->services[$id] = $this->services[$resolvedName]; + + /** @psalm-suppress MixedReturnStatement Yes indeed, service managers can return mixed. */ return $this->services[$resolvedName]; } @@ -247,6 +252,7 @@ public function get(string $id): mixed $this->services[$id] = $service; } + /** @psalm-suppress MixedReturnStatement Yes indeed, service managers can return mixed. */ return $service; } diff --git a/test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php b/test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php new file mode 100644 index 00000000..67f00b87 --- /dev/null +++ b/test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php @@ -0,0 +1,65 @@ +getServiceProvider(); + + $date = $serviceProvider->get(DateTimeImmutable::class); + echo $date->format('Y-m-d H:i:s'); + + $value = $serviceProvider->get('foo'); + assert($value === 'bar'); + } + + public function canInferTypeFromBuild(): void + { + $serviceProvider = $this->getServiceProvider(); + + $date = $serviceProvider->build(DateTimeImmutable::class); + echo $date->format('Y-m-d H:i:s'); + + $value = $serviceProvider->build('foo'); + assert($value === 'bar'); + } + + private function getServiceProvider(): ServiceLocatorInterface + { + $services = [ + 'foo' => 'bar', + DateTimeImmutable::class => new DateTimeImmutable(), + ]; + return new class ($services) implements ServiceLocatorInterface { + public function __construct(private readonly array $services) + { + } + + public function has(string $id): bool + { + return isset($this->services[$id]); + } + + public function build(string $name, ?array $options = null): mixed + { + /** @psalm-suppress MixedReturnStatement Yes indeed, can return mixed. */ + return $this->services[$name] ?? null; + } + + public function get(string $id): mixed + { + /** @psalm-suppress MixedReturnStatement Yes indeed, can return mixed. */ + return $this->services[$id] ?? null; + } + }; + } +}