I know this is a PHPStan extension. However nowadays it is not uncommon for people to run both PHPStan and Psalm on a project simultaneously since the feature set is not completely equivalent. And Psalm currently does not have anything similar to the functionality of this extension.
In my project, I did this to provide support for Dibi. It may be a good starting point if someone decides to give it a shot:
use PhpParser\Node\Scalar\String_;
use PHPStan\Reflection\ReflectionProvider\DummyReflectionProvider;
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\VerbosityLevel;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
use Psalm\Type;
use Psalm\Type\Union;
use staabm\PHPStanDba\DibiReflection\DibiReflection;
use DibiConnection;
final class DibiMethodReturnTypeProvider implements MethodReturnTypeProviderInterface
{
public static function getClassLikeNames(): array
{
return [
DibiConnection::class,
];
}
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union
{
if (!in_array($event->getMethodNameLowercase(), ['fetchall', 'fetch', 'fetchpairs'], true)) {
return null;
}
ReflectionProviderStaticAccessor::registerInstance(new DummyReflectionProvider());
$cache = require(__DIR__.'/../.dba.cache.php');
$records = $cache['records'];
$query = $event->getCallArgs()[0]->value;
assert($query instanceof String_);
$queryString = self::replacePlaceholders($query->value);
if (array_key_exists($queryString, $records) === false) {
return null;
}
$result = $records[$queryString]['result'][3];
assert($result instanceof ConstantArrayType);
$stringToParse = match ($event->getMethodNameLowercase()) {
'fetchall' => 'list<'.$result->describe(VerbosityLevel::precise()).'>',
'fetch' => '?' . $result->describe(VerbosityLevel::precise()),
'fetchpairs' => 'list<'.$result->getFirstIterableValueType()->describe(VerbosityLevel::precise()).'>',
};
return Type::parseString($stringToParse);
}
private static function replacePlaceholders(string $queryString): string
{
$rewriteQuery = (new DibiReflection())->rewriteQuery($queryString);
return trim($rewriteQuery ?? '');
}
}
final readonly class DibiConnection
{
public function __construct(
private Connection $connection
) {
}
final public function query(mixed ...$args): Result
{
/** @throws void */
return $this->connection->query($args);
}
public function fetch(mixed ...$args): ?array
{
/** @throws void */
return $this->connection->query($args)
->setRowClass(null)
->fetch();
}
/**
* @return list<array<string, mixed>>
*/
public function fetchAll(mixed ...$args): array
{
/** @throws void */
return $this->connection->query($args)
->setRowClass(null)
->fetchAll();
}
public function fetchSingle(mixed ...$args): mixed
{
/** @throws void */
return $this->connection->query($args)
->setRowClass(null)
->fetchSingle();
}
/**
* @return list<mixed>
*/
public function fetchPairs(mixed ...$args): array
{
/** @throws void */
return $this->connection->query($args)
->setRowClass(null)
->fetchPairs();
}
}
It turns out that it is fairly easy to convert PHPStan types to Psalm types by calling Type::parseString($phpstanType->describe(VerbosityLevel::precise()))
I know this is a PHPStan extension. However nowadays it is not uncommon for people to run both PHPStan and Psalm on a project simultaneously since the feature set is not completely equivalent. And Psalm currently does not have anything similar to the functionality of this extension.
In my project, I did this to provide support for
Dibi
. It may be a good starting point if someone decides to give it a shot:It turns out that it is fairly easy to convert PHPStan types to Psalm types by calling
Type::parseString($phpstanType->describe(VerbosityLevel::precise()))