🎡

ttskch/paginator-bundleでlimit値の上限を設定する方法

2024/08/25に公開

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