ttskch/paginator-bundleでlimit値の上限を設定する方法
ttskch/paginator-bundleを使い始めました。
基本的にシンプルに作られており、Symfonyのフォームともうまく連携できるため、非常に便利です。
limitの値について
デフォルト値はYAMLファイルで設定できますが、上限値を設定することができません。
また、Criteriaオブジェクトで事前にlimit値を設定していても、ページネーション実行時にクエリパラメータで上書きされてしまいます。
これにより、リクエスト時のクエリパラメータが優先されます。
例えば、limit に 20000 という非常に大きな値を指定しても、そのままリクエストが実行されるため、システムが攻撃の対象になりやすくなります。
limit値の制御方法
ttshch/paginator-bundleにはCriteriaというクラスがあります。
このクラスの getFormTypeClass
でメソッドで返されるFormTypeをカスタマイズすることで、リクエスト時のクエリパラメータを制御できます。
そのため、独自のCriteriaクラスを拡張し、新たにFormTypeを作成して、DataTransformer を利用することでリクエストパラメータを適切に制御できます。ttskch/paginator-bundle はSymfonyの機能をうまく活用しており、容易に拡張可能です。
DataTransformerクラスを用意する
symfony/formでは、データの変換を行うためにDataTransformerという機能があります。
詳しい使い方は How to Use Data Transformers を参照いただければと思います。
今回は ApiParamLimitTransformer
をというクラスを新たに用意します。
<?php
declare(strict_types=1);
namespace App\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
/**
* @implements DataTransformerInterface<int|null, int|null>
*/
final class ApiParamLimitTransformer implements DataTransformerInterface
{
public const LIMIT_MAX = 2000;
public function transform(mixed $value): mixed
{
return $value;
}
public function reverseTransform(mixed $value): mixed
{
return min($value, self::LIMIT_MAX);
}
}
ApiCriteriaTypeクラスを作る
独自のApiCriteriaTypeクラスを作って、先程用意したDataTransformerの設定をします。
<?php
declare(strict_types=1);
namespace App\Form;
use App\Form\DataTransformer\ApiParamLimitTransformer;
use App\TtskchPagination\Criteria\ApiCriteria;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Ttskch\PaginatorBundle\Form\CriteriaType;
class ApiCriteriaType extends AbstractType
{
public function __construct(private readonly ApiParamLimitTransformer $apiParamLimitTransformer) {}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->get('limit')
->addModelTransformer($this->apiParamLimitTransformer)
;
}
public function getParent(): string
{
return CriteriaType::class;
}
}
SymfonyのFormTypeでは、元のFormTypeを拡張する場合、extends を使うのではなく、getParent メソッドで拡張対象のFormTypeを指定します。
また、buildForm メソッド内で addModelTransformer を使い、先ほど作成した ApiParamLimitTransformer を設定します。
Criteriaクラスを用意する
<?php
declare(strict_types=1);
namespace App\Criteria;
use App\Form\ApiCriteriaType;
use Ttskch\PaginatorBundle\Criteria\AbstractCriteria;
class ApiCriteria extends AbstractCriteria
{
public function getFormTypeClass(): string
{
return ApiCriteriaType::class;
}
}
このように ApiCriteria クラスを作成します。また、ApiCriteriaType クラスでは configureOptions メソッドを使ってオプションの設定を行います。
<?php
declare(strict_types=1);
namespace App\Form;
use App\Form\DataTransformer\ApiParamLimitTransformer;
use App\TtskchPagination\Criteria\ApiCriteria;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Ttskch\PaginatorBundle\Form\CriteriaType;
class ApiCriteriaType extends AbstractType
{
public function __construct(private readonly ApiParamLimitTransformer $apiParamLimitTransformer) {}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->get('limit')
->addModelTransformer($this->apiParamLimitTransformer)
;
}
public function getParent(): string
{
return CriteriaType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ApiCriteria::class,
'csrf_protection' => false,
]);
}
}
ページネーションの実行
以下のように実行することで、limit の最大値を2000とした形でページネーションを行えます。
$qb = $this->userRepository->createQueryBuilder('u');
$criteria = new ApiCriteria('id');
$this->paginator->initialize(new QueryBuilderSlicer($qb), new QueryBuilderCounter($qb), $criteria);
Discussion