PHPackages                             verclam/doctrine-filter-paginator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. verclam/doctrine-filter-paginator

ActiveSymfony-bundle

verclam/doctrine-filter-paginator
=================================

Attribute-based filtering, pagination, and ordering for Symfony + Doctrine

v0.0.1(1mo ago)00AGPL-3.0-or-laterPHPPHP &gt;=8.1CI failing

Since Mar 25Pushed 1mo agoCompare

[ Source](https://github.com/Verclam/doctrine-filter-paginator)[ Packagist](https://packagist.org/packages/verclam/doctrine-filter-paginator)[ RSS](/packages/verclam-doctrine-filter-paginator/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (7)Versions (2)Used By (0)

Verclam Doctrine Filter Paginator
=================================

[](#verclam-doctrine-filter-paginator)

Attribute-based filtering, pagination, and ordering for Symfony + Doctrine.

Define your filtering logic directly on DTO properties using PHP 8 attributes — no manual query building needed.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Symfony &gt;= 6.4
- Doctrine ORM &gt;= 2.17

Installation
------------

[](#installation)

```
composer require verclam/doctrine-filter-paginator
```

If you're using Symfony Flex, the bundle is registered automatically. Otherwise, add it to `config/bundles.php`:

```
return [
    // ...
    Verclam\DoctrineFilterPaginator\VerclamDoctrineFilterPaginatorBundle::class => ['all' => true],
];
```

Configuration
-------------

[](#configuration)

The bundle works out of the box with no configuration. Optional settings:

```
# config/packages/verclam_doctrine_filter_paginator.yaml
verclam_doctrine_filter_paginator:
    timezone: 'Europe/Paris'       # Default timezone for date filtering (BETWEEN operator)
    total_records_key: 'totalRecords' # Key name for total count in paginated results
    results_key: 'results'            # Key name for results in paginated results
```

### Result Keys Priority

[](#result-keys-priority)

The result array keys can be configured at three levels (highest priority first):

1. **Controller** — per-endpoint override via setter:

    ```
    $dto->setTotalRecordsKey('total');
    $dto->setResultsKey('data');
    $result = $manager->getResult($dto);
    // Returns: ['total' => 150, 'data' => Iterator]
    ```
2. **DTO class** — per-DTO override in the constructor:

    ```
    class ProductPaginationDto extends AbstractFiltersDTO
    {
        public function __construct()
        {
            $this->setTotalRecordsKey('total');
            $this->setResultsKey('data');
        }
    }
    ```
3. **Bundle config** — project-wide default (see above)

Usage
-----

[](#usage)

### 1. Create a Filter DTO

[](#1-create-a-filter-dto)

Extend `AbstractFiltersDTO` and define your filters using attributes:

```
use Verclam\DoctrineFilterPaginator\Attributes\FilterBy;
use Verclam\DoctrineFilterPaginator\Attributes\LeftJoin;
use Verclam\DoctrineFilterPaginator\Attributes\OrderBy;
use Verclam\DoctrineFilterPaginator\DTO\AbstractFiltersDTO;

class ProductPaginationDto extends AbstractFiltersDTO
{
    public const CLASS_NAME = Product::class;
    public const ALIAS = 'p';

    #[FilterBy(property: 'name', operator: FilterBy::LIKE)]
    public ?string $name = null;

    #[FilterBy(property: 'status', operator: FilterBy::EQUAL)]
    public ?string $status = null;

    #[FilterBy(property: 'price', operator: FilterBy::BETWEEN, options: ['dataTypes' => FilterBy::DATA_TYPES_STRING])]
    public ?string $price = null;

    #[FilterBy(property: 'category', operator: FilterBy::IN)]
    #[LeftJoin('category')]
    public ?array $categoryIds = null;

    #[OrderBy(property: 'createdAt')]
    public string $orderByCreatedAt = 'DESC';
}
```

### 2. Use in a Controller

[](#2-use-in-a-controller)

```
use Verclam\DoctrineFilterPaginator\FilterPagerManager;
use Symfony\Component\HttpKernel\Attribute\MapQueryString;

#[Route('/api/products', methods: ['GET'])]
public function list(
    #[MapQueryString] ProductPaginationDto $dto,
    FilterPagerManager $manager,
): JsonResponse {
    $result = $manager->getResult($dto);

    // $result = [
    //     'totalRecords' => 150,
    //     'results'      => Iterator,
    // ]

    return $this->json($result, 200, [], ['groups' => ['product_basic']]);
}
```

Attributes Reference
--------------------

[](#attributes-reference)

### `#[FilterBy]`

[](#filterby)

Defines a filter on an entity property. Repeatable.

```
#[FilterBy(property: 'entityProperty', operator: FilterBy::OPERATOR, options: [...])]
```

**Operators:**

OperatorDescription`FilterBy::EQUAL`Exact match (`=`)`FilterBy::NOT_EQUAL`Not equal (`!=`)`FilterBy::GREATER_THAN`Greater than (`>`)`FilterBy::GREATER_THAN_OR_EQUAL`Greater than or equal (`>=`)`FilterBy::LESS_THAN`Less than (` FilterCondition::OR])]
#[FilterBy(property: 'sharedWith', operator: FilterBy::MEMBER_OF, options: ['filterCondition' => FilterCondition::OR])]
public ?int $userId = null;
```

### Custom DQL Conditions

[](#custom-dql-conditions)

```
use Verclam\DoctrineFilterPaginator\Enum\CustomDQLValueOption;

// Ignore the DTO value, use raw DQL
#[FilterBy(
    property: 'custom_dql_expression',
    operator: FilterBy::CUSTOM_CONDITION,
    options: ['dqlOptions' => CustomDQLValueOption::IGNORE_VALUE],
)]
public ?string $customFilter = null;

// Use the DTO value as a named parameter
#[FilterBy(
    property: 'custom_dql_expression',
    operator: FilterBy::CUSTOM_CONDITION,
    options: ['dqlOptions' => CustomDQLValueOption::VALUE_AS_PARAMETER],
)]
public ?string $customFilter = null;
```

### Custom Select (Non-Paginated)

[](#custom-select-non-paginated)

Override the SELECT clause for aggregate queries:

```
$dto = new ProductPaginationDto();
$dto->customSelectDQL = 'COUNT(p.id)';

$result = $manager->getResult($dto); // Returns raw query result (no pagination)
```

### Get All Without Filtering

[](#get-all-without-filtering)

Pass a class name directly:

```
$result = $manager->getResult(null, Product::class);
```

Development
-----------

[](#development)

```
# Install dependencies
composer install

# Run PHPStan
composer phpstan

# Check coding standards
composer phpcs:check

# Fix coding standards
composer phpcs:format
```

License
-------

[](#license)

AGPL-3.0-or-later

###  Health Score

33

—

LowBetter than 75% of packages

Maintenance90

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/d32aa8893f309d003a69d389d521536d0368ff2f3529c63636b6bc9337ec1cb2?d=identicon)[kalilov](/maintainers/kalilov)

---

Top Contributors

[![kalilovsky](https://avatars.githubusercontent.com/u/79199532?v=4)](https://github.com/kalilovsky "kalilovsky (2 commits)")

###  Code Quality

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/verclam-doctrine-filter-paginator/health.svg)

```
[![Health](https://phpackages.com/badges/verclam-doctrine-filter-paginator/health.svg)](https://phpackages.com/packages/verclam-doctrine-filter-paginator)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
