Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ServiceLocatorInterface#get() generic #230

Merged
merged 4 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@
<code><![CDATA[MixedPluginManager]]></code>
</UnusedClass>
</file>
<file src="test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php">
<UnusedClass>
<code><![CDATA[ServiceLocatorInterfaceConsumer]]></code>
</UnusedClass>
</file>
<file src="test/StaticAnalysis/ServiceManagerConfiguration.php">
<UnusedClass>
<code><![CDATA[ServiceManagerConfiguration]]></code>
Expand Down
4 changes: 2 additions & 2 deletions src/PluginManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function validate(mixed $instance): void;
/**
* @template TRequestedInstance extends InstanceType
* @psalm-param class-string<TRequestedInstance>|string $id Service name of plugin to retrieve.
* @psalm-return ($id is class-string ? TRequestedInstance : InstanceType)
* @psalm-return ($id is class-string<TRequestedInstance> ? TRequestedInstance : InstanceType)
* @throws Exception\ServiceNotFoundException If the manager does not have
* a service definition for the instance, and the service is not
* auto-invokable.
Expand All @@ -43,7 +43,7 @@ public function get(string $id): mixed;
*
* @template TRequestedInstance extends InstanceType
* @psalm-param string|class-string<TRequestedInstance> $name
* @psalm-return ($name is class-string ? TRequestedInstance : InstanceType)
* @psalm-return ($name is class-string<TRequestedInstance> ? TRequestedInstance : InstanceType)
* @throws Exception\ServiceNotFoundException If no factory/abstract
* factory could be found to create the instance.
* @throws Exception\ServiceNotCreatedException If factory/delegator fails
Expand Down
14 changes: 13 additions & 1 deletion src/ServiceLocatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;

/**
* Interface for service locator
*/
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<T> $name
Expand All @@ -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<T> $id
* @psalm-return ($id is class-string<T> ? T : mixed)
* @throws ContainerExceptionInterface Error while retrieving the entry.
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
*/
public function get(string $id);
}
6 changes: 6 additions & 0 deletions src/ServiceManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand All @@ -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;
}

Expand All @@ -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];
}

Expand All @@ -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;
}

Expand Down
65 changes: 65 additions & 0 deletions test/StaticAnalysis/ServiceLocatorInterfaceConsumer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace LaminasTest\ServiceManager\StaticAnalysis;

use DateTimeImmutable;
use Laminas\ServiceManager\ServiceLocatorInterface;

use function assert;

final class ServiceLocatorInterfaceConsumer
{
public function canInferTypeFromGet(): void
{
$serviceProvider = $this->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;
}
};
}
}