I have a php class containing a collection of class. It uses an array with classname as key and instance as value. So I have a getter that takes a classname and returns the corresponding instance (or null if not find). I'm trying through docblock to specify that the returned object is the same as passed classname
public function getService(string $service): ?object
{
if ($this->hasService($service)) {
return $this->services[$service];
}
return null;
}
So I've tried to do this :
/**
* @template T
* @var array<class-string<T>, T>
*/
private array $services = [];
/**
* @template T
* @psalm-param class-string<T> $service
* @return T|null
*/
public function getService(string $service): ?object
{
if ($this->hasService($service)) {
return $this->services[$service];
}
return null;
}
With that, the function do what I expects. But psalm returns me 2 errors :
ERROR: InvalidReturnType - src/Services/ServiceManager.php:189:16 - The declared return type '(T:fn-marmot\brick\services\servicemanager::getservice as object)|null' for Marmot\Brick\Services\ServiceManager::getService is incorrect, got 'null|object' (see https://psalm.dev/011)
* @return T|null
ERROR: InvalidReturnStatement - src/Services/ServiceManager.php:194:20 - The inferred type 'object' does not match the declared return type '(T:fn-marmot\brick\services\servicemanager::getservice as object)|null' for Marmot\Brick\Services\ServiceManager::getService (see https://psalm.dev/128)
return $this->services[$service];
You need a class-string-map
for that: https://psalm.dev/r/134a1df401
<?php
interface ServiceInterface {}
class C {
/**
* @var class-string-map<T as ServiceInterface, T>
*/
private array $services = [];
/**
* @template T of ServiceInterface
* @param class-string<T> $name
* @return T
*/
public function getService(string $name): object {
if (!isset($this->services[$name])) { throw new RuntimeException; }
return $this->services[$name];
}
}