0

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];
Gashmob
  • 5
  • 4

1 Answers1

0

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];
    }
}
weirdan
  • 2,499
  • 23
  • 27