初めに
前略、PHPStanの動的戻り値拡張を試してみたのでまとめます。
動的戻り値拡張とは
動的戻り値拡張とは、動的に戻り値が決まるメソッドの戻り値を定義することができる機能です。
極端な例ですが、以下のdynamicReturnMethod()メソッドは受け取った引数をそのまま返すメソッドです。そのため、戻り値は呼び出し側の引数に何が詰められるかによって決まり、PHPDocに記載することができません。
<?php
namespace Path\To\Project\analyseObject;
class DynamicMehodReturn
{
function dynamicReturnMethod(mixed $arg)
{
// ★戻り値がメソッド引数の型に左右されてしまうため定義ができない
return $arg;
}
}
2025/01/07 追記
上記の例について、執筆当初はできないと思っていたのですが、コメントでご指摘いただき以下のように@template
を利用することで定義できることが分かりました
/**
* @template T
* @param T $arg
* @return T
*/
function dynamicReturnMethod(mixed $arg)
{
return $arg;
}
※この記事では動的戻り値拡張を扱いたいので、@template
を用いずに進めます。
そのため、呼び出し側では戻り値の型がmixedになってしまい、正しい解析を行うことができなくなってしまいます。
<?php
namespace Path\To\Project\analyseObject;
use DateTime;
class CallDynamicMethod
{
function classDynamicMethod(int $arg): void
{
$dynamicMethodClass = new DynamicMehodReturn();
$response1 = $dynamicMethodClass->dynamicReturnMethod($arg);
\PHPStan\dumpType($response1);
$response2 = $dynamicMethodClass->dynamicReturnMethod("test");
\PHPStan\dumpType($response2);
$response3 = $dynamicMethodClass->dynamicReturnMethod(new DateTime());
\PHPStan\dumpType($response3);
}
}
> ./vendor/bin/phpstan analyse .\src\analyseObject\CallDynamicMethod.php -v
Note: Using configuration file C:\Path\to\project\phpstan\phpstan.neon.
1/1 [============================] 100% 1 sec
------ --------------------------
Line CallDynamicMethod.php
------ --------------------------
:13 Dumped type: mixed
✏️ CallDynamicMethod.php
:15 Dumped type: mixed
✏️ CallDynamicMethod.php
:17 Dumped type: mixed
✏️ CallDynamicMethod.php
------ --------------------------
動的戻り値拡張を作ってみる
そこで、PHPStanの動的戻り値拡張を利用してみましょう。
動的戻り値拡張は以下のクラスを継承することで作成することができます。
namespace PHPStan\Type;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
interface DynamicMethodReturnTypeExtension
{
public function getClass(): string;
public function isMethodSupported(MethodReflection $methodReflection): bool;
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): ?Type;
}
それぞれのメソッドの役割はこちらです。
メソッド | 役割 |
---|---|
getClass() |
動的戻り値拡張の対象メソッドがあるクラスを指定 |
isMethodSupported() |
動的戻り値拡張の対象のメソッド名を指定 |
getTypeFromMethodCall() |
型を戻り値で指定する |
今回は以下のように作成しました。
<?php
namespace Path\To\Project\extension;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
class TestDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return \Path\To\Project\analyseObject\DynamicMehodReturn::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'dynamicReturnMethod';
}
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): ?Type
{
if (count($methodCall->getArgs()) !== 1) {
return null;
}
$arg = $methodCall->getArgs()[0]->value;
return $scope->getType($arg);
}
}
その後、phpstan.neon
に以下のように記載し、PHPStanに拡張を認識させます。
以上で準備OKです。
services:
-
class: Path\To\Project\extension\TestDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
PHPStanで解析を行うと、戻り値が設定されていました。
> ./vendor/bin/phpstan analyse .\src\analyseObject\CallDynamicMethod.php -v
Note: Using configuration file C:\Path\to\project\phpstan\phpstan.neon.
1/1 [============================] 100% 1 sec
------ --------------------------
Line CallDynamicMethod.php
------ --------------------------
:13 Dumped type: int
✏️ CallDynamicMethod.php
:15 Dumped type: 'test'
✏️ CallDynamicMethod.php
:17 Dumped type: DateTime
✏️ CallDynamicMethod.php
------ --------------------------
※15行目の型がStringではなく'test'と出ているのは、PHPStanがConstantStringType
と判断しているためです。
終わりに
PHPStanには様々な拡張機能があって便利ですね、他にも調べてみようと思います。
ここまでご覧いただきありがとうございました!