【PHPフレームワークFlow】getObjectByIdentifierの戻り値をPHPStanに認識させる

# 初めに
PHPフレームワークFlowのgetObjectByIdentifier()メソッドは動的に戻り値を変えるメソッドです。そのため、PHPStanの解析時に型の定義ができません。今回は、PHPStanの動的戻り値拡張を用いてこのメソッドに型を定義していきたいと思います。

# 課題
## getObjectByIdentifier()とはIDからエンティティを取得するメソッド
`getObjectByIdentifier()`はFlowのPersistenceManagerのメソッドです。
UUIDとドメインモデルのクラス名を引数に、対応するドメインモデルを取得することができます。

```php: getObjectByIdentifier()
    /**
     * Returns the object with the (internal) identifier, if it is known to the
     * backend. Otherwise NULL is returned.
     *
     * @param mixed $identifier
     * @param class-string|null $objectType
     * @param boolean $useLazyLoading Set to true if you want to use lazy loading for this object
     * @return object|null The object for the identifier if it is known, or NULL
     * @api
     */
    public function getObjectByIdentifier($identifier, string $objectType = null, bool $useLazyLoading = false);
```

## PHPStanはgetObjectByIdentifierの戻り値を認識しない
このメソッドのreturnの型は第二引数のドメインモデルの型によって決まります。
そのため、そのままではPHPStanは型を認識してくれません。

```php: 解析対象
class TestContoller
{
    ~中略~
    
    public function testAction(): void
    {
        // Itemオブジェクトのエンティティを取得
        $oldItem = $this->persistenceManager->getObjectByIdentifier(
            $uuid,
            Item::class
        );
        // nullチェック
        if (is_null($oldItem)) {
            $this->view->assign('value', 'NG');
            return;
        }
        // 型を出力してみる
        \PHPStan\dumpType($oldItem);
        $oldItem->setPrice($price);
    }
}
```


```powershell: 解析結果
> ./bin/phpstan analyse
Note: Using configuration file C:\Users\rogto\workspace\php\flow\Quickstart\phpstan.neon.
 72/72 [============================] 100%

 ------ -------------------------------------------------------------------------------------- 
  Line   Packages/Application/Neos.Welcome/Classes/Controller/TestController.php     
 ------ --------------------------------------------------------------------------------------
  :186   Dumped type: object
         ✏️  Packages/Application/Neos.Welcome/Classes/Controller/TestController.php
  :187   Call to an undefined method object::setPrice().
         ✏️  Packages/Application/Neos.Welcome/Classes/Controller/TestController.php
 ------ --------------------------------------------------------------------------------------
```

# 解決策
## 動的戻り値拡張を用いて戻り値の型を認識させる
ということで、動的戻り値拡張を使って解決していきましょう。
動的戻り値拡張についてはこちらの記事で解説しているためそちらをご参照ください。

https://qiita.com/nnhkrnk/items/3c49b35fe1d331094a36


```php: 動的戻り値拡張でgetObjectByIdentifier()の戻り値を定義
<?php

namespace Neos\Welcome\PHPStan\Extension;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class GetObjectByIdentifierDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    public function getClass(): string
	{
		return \Neos\Flow\Persistence\PersistenceManagerInterface::class;
	}

	public function isMethodSupported(MethodReflection $methodReflection): bool
	{
		return $methodReflection->getName() === 'getObjectByIdentifier';
	}

	public function getTypeFromMethodCall(
		MethodReflection $methodReflection,
		MethodCall $methodCall,
		Scope $scope
	): ?Type
	{
        // 第二引数の型から戻り値の型を定義
		$args = $methodCall->getArgs();
        $domainModelClass = $scope->getType($args[1]->value);

        // nullもあり得るので定義
        $types[] = new ObjectType($domainModelClass->getValue());
		$types[] = new NullType();

		return TypeCombinator::union(...$types);
	}
}

```

無事に型が読み込まれました！

```powershell
> ./bin/phpstan analyse
Note: Using configuration file C:\Users\rogto\workspace\php\flow\Quickstart\phpstan.neon.
 72/72 [============================] 100%

 ------ -------------------------------------------------------------------------------------- 
  Line   Packages/Application/Neos.Welcome/Classes/Controller/TestController.php     
 ------ --------------------------------------------------------------------------------------
  :186   Dumped type: Neos\Welcome\Domain\Model\Item
         ✏️  Packages/Application/Neos.Welcome/Classes/Controller/TestController.php
 ------ --------------------------------------------------------------------------------------
```

# 終わりに
今回はgetObjectByIdentifier()の戻り値の型を定義する方法を解説しました。
戻り値の型にnullを付与していますが、他のドメイン経由でUUIDを指定する場合などはnullになる可能性は少なく、nullチェックをしていない場合もあるかと思います。その辺をどうするかは考えてもいいかもしれませんね。

ここまでご覧いただきありがとうございました！