{"id":15021788,"url":"https://github.com/ttskch/doctrine-orm-criteria","last_synced_at":"2026-02-06T17:36:13.038Z","repository":{"id":254128824,"uuid":"845553719","full_name":"ttskch/doctrine-orm-criteria","owner":"ttskch","description":"Doctrine ORM Criteria allows you to separate any complex \"search condition\" as a Criteria with a specialized API for QueryBuilder of doctrine/orm.","archived":false,"fork":false,"pushed_at":"2025-11-18T13:42:26.000Z","size":69,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-18T16:16:51.523Z","etag":null,"topics":["criteria","doctrine","doctrine-orm","symfony"],"latest_commit_sha":null,"homepage":"https://packagist.org/packages/ttskch/doctrine-orm-criteria","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ttskch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-21T13:25:02.000Z","updated_at":"2025-11-18T13:41:53.000Z","dependencies_parsed_at":"2025-07-09T15:42:31.701Z","dependency_job_id":"24476ce5-ac30-4cfb-ab78-9262a80a7526","html_url":"https://github.com/ttskch/doctrine-orm-criteria","commit_stats":{"total_commits":28,"total_committers":2,"mean_commits":14.0,"dds":0.0357142857142857,"last_synced_commit":"1a96475f632e659dcc2fb414c64e010c03f1fdbe"},"previous_names":["ttskch/doctrine-orm-criteria"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ttskch/doctrine-orm-criteria","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fdoctrine-orm-criteria","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fdoctrine-orm-criteria/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fdoctrine-orm-criteria/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fdoctrine-orm-criteria/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ttskch","download_url":"https://codeload.github.com/ttskch/doctrine-orm-criteria/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fdoctrine-orm-criteria/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29170284,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T16:33:35.550Z","status":"ssl_error","status_checked_at":"2026-02-06T16:33:30.716Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["criteria","doctrine","doctrine-orm","symfony"],"created_at":"2024-09-24T19:57:03.980Z","updated_at":"2026-02-06T17:36:13.013Z","avatar_url":"https://github.com/ttskch.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Doctrine ORM Criteria\n\n[![](https://github.com/ttskch/doctrine-orm-criteria/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/ttskch/doctrine-orm-criteria/actions/workflows/ci.yaml?query=branch:main)\n[![codecov](https://codecov.io/gh/ttskch/doctrine-orm-criteria/graph/badge.svg?token=gu1GDphBHg)](https://codecov.io/gh/ttskch/doctrine-orm-criteria)\n[![Latest Stable Version](https://poser.pugx.org/ttskch/doctrine-orm-criteria/v)](https://packagist.org/packages/ttskch/doctrine-orm-criteria)\n[![Total Downloads](https://poser.pugx.org/ttskch/doctrine-orm-criteria/downloads)](https://packagist.org/packages/ttskch/doctrine-orm-criteria/stats)\n\n## Motivation\n\n[`QueryBuilder`](https://www.doctrine-project.org/projects/doctrine-orm/en/2.20/reference/query-builder.html) of [doctrine/orm](https://www.doctrine-project.org/projects/doctrine-orm/en/2.20/index.html) has a method called [`addCriteria()`](https://www.doctrine-project.org/projects/doctrine-orm/en/2.20/reference/query-builder.html#adding-a-criteria-to-a-query) that allows you to build queries by combining [`Criteria`](https://www.doctrine-project.org/projects/doctrine-orm/en/2.20/reference/working-with-associations.html#filtering-collections) of [doctrine/collections](https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html). This allows you to separate the concerns of \"search conditions\" into a `Criteria`, improving the maintainability of your codebase.\n\nHowever, `Criteria` of doctrine/collections only has a very limited matching language because it is designed to work both on the SQL and the PHP collection level, and therefore cannot be used to build complex queries.\n\nRejoice! **Doctrine ORM Criteria** allows you to separate any complex \"search condition\" as a `Criteria` with a specialized API for `QueryBuilder` of doctrine/orm just like below.\n\n```php\n$qb = (new CriteriaAwareness($fooRepository-\u003ecreateQueryBuilder('f')))\n    -\u003eaddCriteria(new IsPublic(), 'f')\n    -\u003eaddCriteria(new IsAccessibleBy($user), 'f')\n    -\u003eaddCriteria(new CategoryIs($category), 'f')\n    -\u003eaddCriteria(new OrderByRandom(), 'f')\n    -\u003egetQueryBuilder()\n;\n$foos = $qb-\u003egetQuery()-\u003egetResult();\n\n// Or, using the Repository integration:\n\n$foos = $fooRepository-\u003efindByCriteria([\n    new IsPublic(),\n    new IsAccessibleBy($user),\n    new CategoryIs($category),\n    new OrderByRandom(),\n]);\n```\n\n```php\nfinal readonly class IsPublic implements CriteriaInterface\n{\n    public ?\\DateTimeInterface $at;\n\n    public function __construct(?\\DateTimeInterface $at = null)\n    {\n        $this-\u003eat = $at ?? new \\DateTimeImmutable();\n    }\n\n    public function apply(QueryBuilder $qb, string $alias): void\n    {\n        $qb\n            -\u003eandWhere(\"$alias.state = :state\")\n            -\u003eandWhere($qb-\u003eexpr()-\u003eandX(\n                $qb-\u003eexpr()-\u003eorX(\n                    \"$alias.openedAt IS NULL\",\n                    \"$alias.openedAt \u003c= :at\",\n                ),\n                $qb-\u003eexpr()-\u003eorX(\n                    \"$alias.closedAt IS NULL\",\n                    \"$alias.closedAt \u003e :at\",\n                ),\n            ))\n            -\u003esetParameter('state', Foo::STATE_PUBLIC)\n            -\u003esetParameter('at', $this-\u003eat)\n        ;\n    }\n}\n```\n\n## Requirements\n\n* PHP: ^8.1\n* Doctrine ORM: ^2.8|^3.0\n\n## Installation\n\n```shell\n$ composer require ttskch/doctrine-orm-criteria\n```\n\n## Usage\n\n### Basic\n\nYou can create your own `Criteria` by implementing [`CriteriaInterface`](src/Criteria/CriteriaInterface.php) and adding it to [`CriteriaAwareness`](src/CriteriaAwareness.php) to build queries.\n\n```php\nuse App\\Repository\\Criteria\\Foo\\IsPublic;\nuse Ttskch\\DoctrineOrmCriteria\\CriteriaAwareness;\n\n$qb = (new CriteriaAwareness($fooRepository-\u003ecreateQueryBuilder('f')))\n    -\u003eaddCriteria(new IsPublic(), 'f')\n    -\u003egetQueryBuilder()\n;\n```\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Repository\\Criteria\\Foo;\n\nfinal readonly class IsPublic implements CriteriaInterface\n{\n    public ?\\DateTimeInterface $at;\n\n    public function __construct(?\\DateTimeInterface $at = null)\n    {\n        $this-\u003eat = $at ?? new \\DateTimeImmutable();\n    }\n\n    public function apply(QueryBuilder $qb, string $alias): void\n    {\n        $qb\n            -\u003eandWhere(\"$alias.state = :state\")\n            -\u003eandWhere($qb-\u003eexpr()-\u003eandX(\n                $qb-\u003eexpr()-\u003eorX(\n                    \"$alias.openedAt IS NULL\",\n                    \"$alias.openedAt \u003c= :at\",\n                ),\n                $qb-\u003eexpr()-\u003eorX(\n                    \"$alias.closedAt IS NULL\",\n                    \"$alias.closedAt \u003e :at\",\n                ),\n            ))\n            -\u003esetParameter('state', Foo::STATE_PUBLIC)\n            -\u003esetParameter('at', $this-\u003eat)\n        ;\n    }\n}\n```\n\n### Built-in Criteria and utilities\n\nThere are some built-in `Criteria`: `OrderBy`, `Andx`, and `Orx`. Using `Andx` and `Orx`, you can combine multiple `Criteria` to create a new `Criteria`.\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Repository\\Criteria\\Foo;\n\nuse App\\Entity\\User;\nuse Doctrine\\ORM\\QueryBuilder;\nuse Ttskch\\DoctrineOrmCriteria\\Criteria\\CriteriaInterface;\nuse Ttskch\\DoctrineOrmCriteria\\Criteria\\Andx;\nuse Ttskch\\DoctrineOrmCriteria\\Criteria\\Orx;\n\nfinal readonly class IsViewable implements CriteriaInterface\n{\n    public ?\\DateTimeInterface $at;\n\n    public function __construct(\n        public User $me,\n        ?\\DateTimeInterface $at = null,\n    ) {\n        $this-\u003eat = $at ?? new \\DateTimeImmutable();\n    }\n\n    public function apply(QueryBuilder $qb, string $alias): void\n    {\n        (new Andx([\n            new Orx([\n                new IsPublic($this-\u003eat),\n                ...array_map(fn (string $category) =\u003e new CategoryIs($category), Foo::PUBLIC_CATEGORIES),\n            ]),\n            new IsAccessibleBy($this-\u003eme),\n        ]))-\u003eapply($qb, $alias);\n    }\n}\n```\n\nAdditionally, when creating your own `Criteria`, you can use [`AddSelectTrait`](src/Criteria/Traits/AddSelectTrait.php) and [`JoinTrait`](src/Criteria/Traits/JoinTrait.php) to ensure that the `addSelect()` and `join` are IDEMPOTENT even if the `Criteria` is applied multiple times to the `QueryBuilder`.\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Repository\\Criteria\\Foo;\n\nuse App\\Entity\\User;\nuse Doctrine\\ORM\\QueryBuilder;\nuse Ttskch\\DoctrineOrmCriteria\\Criteria\\CriteriaInterface;\nuse Ttskch\\DoctrineOrmCriteria\\Criteria\\Traits\\JoinTrait;\n\nfinal readonly class IsAccessibleBy implements CriteriaInterface\n{\n    use JoinTrait;\n\n    private const string CRITERIA_KEY = 'Foo_IsAccessibleBy'; // some unique key\n\n    public function __construct(public User $me)\n    {\n    }\n\n    public function apply(QueryBuilder $qb, string $alias): void\n    {\n        $userAlias = sprintf('%s_%s_user', self::CRITERIA_KEY, $alias);\n\n        $this-\u003eleftJoin($qb, sprintf('%s.user', $alias), $userAlias);\n\n        $qb\n            -\u003eandWhere(sprintf('%s = :user', $userAlias))\n            -\u003esetParameter('user', $this-\u003eme)\n        ;\n    }\n}\n```\n\n### Integration with Repository\n\nYou can also easily integrate with your repositories using [`CriteriaAwareRepositoryTrait`](src/Repository/CriteriaAwareRepositoryTrait.php).\n\n```diff\n  \u003c?php\n\n  declare(strict_types=1);\n\n  namespace App\\Repository;\n\n  use App\\Entity\\Foo;\n  use Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository;\n  use Doctrine\\Persistence\\ManagerRegistry;\n+ use Ttskch\\DoctrineOrmCriteria\\Repository\\CriteriaAwareRepositoryTrait;\n\n  /**\n   * @extends ServiceEntityRepository\u003cFoo\u003e\n   */\n  class FooRepository extends ServiceEntityRepository\n  {\n+     /** @use CriteriaAwareRepositoryTrait\u003cFoo\u003e */\n+     use CriteriaAwareRepositoryTrait;\n+\n      public function __construct(ManagerRegistry $registry)\n      {\n          parent::__construct($registry, Foo::class);\n      }\n  }\n```\n\n```php\n$foos = $fooRepository-\u003efindByCriteria([\n    new IsPublic(),\n    new IsAccessibleBy($user),\n    new CategoryIs($category),\n    new OrderByRandom(),\n]);\n\n\\PHPStan\\dumpType($foos); // Dumped type: array\u003cApp\\Entity\\Foo\u003e\n\n$foo = $fooRepository-\u003efindOneByCriteria([\n    new IsPublic(),\n    new IsAccessibleBy($user),\n    new CategoryIs($category),\n    new OrderByRandom(),\n]);\n\n\\PHPStan\\dumpType($foo); // Dumped type: App\\Entity\\Foo|null\n\n$count = $fooRepository-\u003ecountByCriteria([\n    new IsPublic(),\n    new IsAccessibleBy($user),\n    new CategoryIs($category),\n]);\n\n\\PHPStan\\dumpType($count); // Dumped type: int\n```\n\n## Getting involved\n\n```shell\n$ composer install\n\n# Develop...\n\n$ composer tests\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fdoctrine-orm-criteria","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fttskch%2Fdoctrine-orm-criteria","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fdoctrine-orm-criteria/lists"}