From a901b679d2fff23856583ff8cd39466b14286759 Mon Sep 17 00:00:00 2001 From: Jens Hatlak Date: Wed, 13 Mar 2024 12:17:50 +0100 Subject: [PATCH 1/4] Make ServiceLocatorInterface::get generic Signed-off-by: Jens Hatlak --- src/ServiceLocatorInterface.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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); } From 85416ee8ed84dc70959e603d8f51a918c5b16933 Mon Sep 17 00:00:00 2001 From: Jens Hatlak Date: Wed, 13 Mar 2024 12:46:12 +0100 Subject: [PATCH 2/4] ServiceManager::get returns mixed Signed-off-by: Jens Hatlak --- src/ServiceManager.php | 6 ++++++ 1 file changed, 6 insertions(+) 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; } From a6149b94d048ca3c3830910b0b9a178f74721868 Mon Sep 17 00:00:00 2001 From: Jens Hatlak Date: Fri, 22 Mar 2024 12:42:26 +0100 Subject: [PATCH 3/4] Add static analysis test Signed-off-by: Jens Hatlak --- psalm-baseline.xml | 5 ++ .../ServiceLocatorInterfaceConsumer.php | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php 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/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; + } + }; + } +} From c936ac703e783ccd491468b72db395b87a971a2d Mon Sep 17 00:00:00 2001 From: Jens Hatlak Date: Fri, 22 Mar 2024 12:53:24 +0100 Subject: [PATCH 4/4] Adapt PluginManagerInterface generics to match SLI Allow PHPStan to understand conditional returns Signed-off-by: Jens Hatlak --- src/PluginManagerInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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