初めに
FlowのRepository
には、DBへのシンプルな問い合わせを定義するcreateQuery()
が用意されています。これにより、SQLを作成することなくDBアクセスができます。しかし、PHPStanではデフォルトでcreateQuery()
を認識してくれません。
今回はFlowのRepositoryクラスのcreateQuery()
をPHPStanに認識させる方法を紹介します。
FlowのcreateQuery()とは
createQuery()
は、FlowのRepository
のメソッドです。
DBへの問い合わせを行うQueryクラスを作成することができます。
(使用例は後述します)
interface RepositoryInterface
{
/**
* Returns a query for objects of this repository
*
* @return QueryInterface
* @api
*/
public function createQuery(): QueryInterface;
}
createQuery()の使い方
取得したい条件を指定後、execute()
を行い、QueryResult
(取得結果を抽象化したクラス)を作成します。
その後、QueryResult
のgetFirst()
やtoArray()
でDBのデータを取得することができます。
取得結果の型はRepository
に対応するドメインモデルとなります。
$query = $this->hogeRepository->createQuery(); // Query型
// QueryResult型
$queryResult = $query->matching(
$query->equals('column', 'value')
)->execute();
$resultData = $queryResult->getFirst(); // Hoge型(hogeRepositoryに対応するドメインモデル型)
$resultDataList = $queryResult->toArray(); // Array<Hoge>型
PHPStanはcreateQuery()を認識しない
PHPStanではcreateQuery()
経由で取得した結果の型を認識することができません。
実際に先ほどの例で取得した値の型を出力してみましたが、型がobject
やarray
などになってしまい、詳細な型が認識できていません。
$query = $this->hogeRepository->createQuery(); // Query型
// QueryResult型
$queryResult = $query->matching(
$query->equals('column', 'value')
)->execute();
$resultData = $queryResult->getFirst(); // Hoge型(hogeRepositoryに対応するドメインモデル型)
$resultDataList = $queryResult->toArray(); // Array<Hoge>型
// 型を確認
\PHPStan\dumpType($query);
\PHPStan\dumpType($queryResult);
\PHPStan\dumpType($resultData);
\PHPStan\dumpType($resultDataList);
./bin/phpstan analyse .\Packages\Application\Neos.Welcome\Classes\Controller\FunctionalTestController.php
Note: Using configuration file C:\Users\rogto\workspace\php\flow\Quickstart\phpstan.neon.
1/1 [============================] 100%
------ ---------------------------------------------------------------------------------------
Line Packages/Application/Neos.Welcome/Classes/Controller/FunctionalTestController.php
------ ---------------------------------------------------------------------------------------
:97 Dumped type: Neos\Flow\Persistence\QueryInterface
:99 Dumped type: Neos\Flow\Persistence\QueryResultInterface
:101 Dumped type: object|null
:103 Dumped type: array
------ ---------------------------------------------------------------------------------------
動的戻り値拡張を用いたいところですが、createQuery()
の戻り値はQuery
であり、QueryResult
を経由してデータを取得します。そのため、動的戻り値拡張だけではうまくいきません。
PHPStanにcreateQueryを認識させる
ということで動的戻り値拡張に加えて、スタブ、ジェネリクスを贅沢に使用し、createQuery()の結果をPHPStanに認識させてみましょう。
-
スタブ & ジェネリクス
-
QueryInterface
とQueryResultInterface
のスタブを作成 - ジェネリクスを使い、ドメインモデルの型を動的に受け取れるようにする
-
-
動的戻り値拡張
-
createQuery()
の動的戻り値拡張を作成 - 戻り値を
QueryInterface
からQueryInterface<Object>
に変更-
Object
はRepository
に対応するドメインモデルをリポジトリ名から判別
-
-
以下実装です。
<?php
namespace Neos\Flow\Persistence;
/**
* @template T of object
*/
interface QueryInterface
{
/**
* @param bool $cacheResult If the result cache should be used
* @return QueryResultInterface<T> The query result
*/
public function execute(bool $cacheResult = false): QueryResultInterface;
/**
* @param array $orderings The property names to order by
* @return QueryInterface<T>
*/
public function setOrderings(array $orderings): QueryInterface;
/**
* @param integer|null $limit
* @return QueryInterface<T>
*/
public function setLimit(?int $limit): QueryInterface;
/**
* @param boolean $distinct
* @return QueryInterface<T>
*/
public function setDistinct(bool $distinct = true): QueryInterface;
/**
* @param integer|null $offset
* @return QueryInterface<T>
*/
public function setOffset(?int $offset): QueryInterface;
/**
* @param object $constraint Some constraint, depending on the backend
* @return QueryInterface<T>
*/
public function matching($constraint): QueryInterface;
}
<?php
namespace Neos\Flow\Persistence;
/**
* @template T of object
*/
interface QueryResultInterface extends \Countable, \Iterator, \ArrayAccess
{
/**
* @return QueryInterface<T>
*/
public function getQuery(): QueryInterface;
/**
* @return T
*/
public function getFirst();
/**
* @return array<T>
*/
public function toArray(): array;
}
<?php
namespace Neos\Welcome\PHPStan\Extension;
use Neos\Flow\Persistence\QueryInterface;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Type;
class createQueryDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return \Neos\Flow\Persistence\Repository::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'createQuery';
}
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): ?Type
{
$repositoryClassName = $scope->getType($methodCall->var)->getClassName();
if (!str_ends_with($repositoryClassName, 'Repository')) {
return null;
}
$withoutRepository = preg_replace('/Repository$/', '', $repositoryClassName);
$domainModelClassName = str_replace('Repository', 'Model', $withoutRepository);
return new GenericObjectType(
QueryInterface::class,
[0 => new ObjectType($domainModelClassName)]
);
}
}
今回の動作確認では不要ですが、QueryInterface
のスタブではexecute()
だけでなくQueryInterface
とQueryResultInterface
を返却するすべてのメソッドでジェネリクスを定義しています。
実際に動作確認した結果がこちらです。
型が認識されていることが確認できました!
./bin/phpstan analyse .\Packages\Application\Neos.Welcome\Classes\Controller\FunctionalTestController.php
Note: Using configuration file C:\Users\rogto\workspace\php\flow\Quickstart\phpstan.neon.
1/1 [============================] 100%
------ ---------------------------------------------------------------------------------------
Line Packages/Application/Neos.Welcome/Classes/Controller/FunctionalTestController.php
------ ---------------------------------------------------------------------------------------
:97 Dumped type: Neos\Flow\Persistence\QueryInterface<Neos\Welcome\Domain\Model\Hoge>
:99 Dumped type: Neos\Flow\Persistence\QueryResultInterface<Neos\Welcome\Domain\Model\Hoge>
:101 Dumped type: Neos\Welcome\Domain\Model\Hoge
:103 Dumped type: array<Neos\Welcome\Domain\Model\Hoge>
------ ---------------------------------------------------------------------------------------
終わりに
PHPStanの各拡張機能は便利ですが、組み合わせることでさらに便利になります。いろいろ組み合わせて、フレームワーク固有の問題を解決していきたいです。
ここまでご覧いただきありがとうございました!