{"id":15529407,"url":"https://github.com/frankdejonge/doctrine-query-specification","last_synced_at":"2025-04-09T10:09:19.144Z","repository":{"id":45934870,"uuid":"45713995","full_name":"frankdejonge/doctrine-query-specification","owner":"frankdejonge","description":"Specification based querying for Doctrine2","archived":false,"fork":false,"pushed_at":"2024-07-25T15:22:30.000Z","size":37,"stargazers_count":74,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T02:35:17.304Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/frankdejonge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2015-11-06T23:34:59.000Z","updated_at":"2024-11-15T13:34:34.000Z","dependencies_parsed_at":"2024-12-25T13:08:19.294Z","dependency_job_id":"a375e9fc-7336-40ce-bae9-3df65cf20cfd","html_url":"https://github.com/frankdejonge/doctrine-query-specification","commit_stats":{"total_commits":47,"total_committers":9,"mean_commits":5.222222222222222,"dds":0.2978723404255319,"last_synced_commit":"61ecad85ee9fa6dcee19ddbabb1f11d45cba79b3"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankdejonge%2Fdoctrine-query-specification","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankdejonge%2Fdoctrine-query-specification/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankdejonge%2Fdoctrine-query-specification/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankdejonge%2Fdoctrine-query-specification/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frankdejonge","download_url":"https://codeload.github.com/frankdejonge/doctrine-query-specification/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018061,"owners_count":21034048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-10-02T11:17:51.785Z","updated_at":"2025-04-09T10:09:19.119Z","avatar_url":"https://github.com/frankdejonge.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Doctrine2 - Specification based querying.\n\n[![.github/workflows/main.yaml](https://github.com/frankdejonge/doctrine-query-specification/actions/workflows/main.yaml/badge.svg)](https://github.com/frankdejonge/doctrine-query-specification/actions/workflows/main.yaml)\n\nThis packages eases the translation of domain questions to things doctrine can understand.\n\nQuery specifications allow you to hook into three stages of the query building process.\n\n1. Applying constraints\n2. Modifying the query builder.\n3. Modifying the query.\n\nThis allows you to encapsulate query logic in bite-sized, small, object. When naming these\nobjects you can take into account what purpose they fulfill.\n\n## Installation:\n\n```bash\ncomposer require frankdejonge/doctrine-query-specification\n```\n\n## Effect\n\nTurn code like this:\n\n```php\n// Get the newest 5 articles, needed for front-page\n$qb = $articleRepository-\u003ecreateQueryBuilder('a');\n$query = $qb\n    -\u003ewhere($qb-\u003eexpr()-\u003eeq('a.published', true))\n    -\u003egetQuery()\n    -\u003egetResult();\n```\n\nInto this:\n\n```php\n$articles = $articleRepositoryFieldEquals-\u003efindBySpecification(new FrontPageArticles());\n```\n\n\n## Examples\n\n### Specification aware repositories.\n\n```php\nuse FrankDeJonge\\DoctrineQuerySpecification\\SpecificationAwareEntityRepository;\nuse FrankDeJonge\\DoctrineQuerySpecification\\SpecificationAwareRepository;\nuse FrankDeJonge\\DoctrineQuerySpecification\\SpecificationAwareRepositoryTrait;\n\nclass ArticleRepository extends SpecificationAwareEntityRepository\n{\n\n}\n// OR\nclass ArticleRepository implements SpecificationAwareRepository\n{\n    use SpecificationAwareRepositoryTrait;\n}\n```\n\n### Query constraints.\n\n```php\n\u003c?php\n\nuse Doctrine\\ORM\\QueryBuilder;\nuse FrankDeJonge\\DoctrineQuerySpecification\\QueryConstraint;\n\nclass IsPublished implements QueryConstraint\n{\n    public function asQueryConstraint(QueryBuilder $builder, string $rootAlias): ?object\n    {\n        $expr = $builder-\u003eexpr();\n        \n        return $expr-\u003eeq(\"{$rootAlias}.published\", true);\n    }\n}\n\n$publishedArticles = $articleRepository-\u003efindBySpecification(new IsPublished);\n```\n\nQuery constrains can also accept user-provided input in constructors. When doing so, use\nparameterized queries to protect yourself against SQL-injections.\n\n```php\n\u003c?php\n\nuse Doctrine\\ORM\\QueryBuilder;\nuse FrankDeJonge\\DoctrineQuerySpecification\\QueryConstraint;\n\nclass ArticleHasNameLike implements QueryConstraint\n{\n    /** @var string */\n    private $name;\n\n    public function __construct(string $name)\n    {\n        $this-\u003ename = $name;\n    }\n\n    public function asQueryConstraint(QueryBuilder $builder, string $rootAlias): ?object\n    {\n        $expr = $builder-\u003eexpr();\n        $builder-\u003esetParameter('name_search', $this-\u003ename);\n        \n        return $expr-\u003elike(\"{$rootAlias}.name\", ':name_search');\n    }\n}\n\n$publishedArticles = $articleRepository-\u003efindBySpecification(new ArticleHasNameLike('Awesome Name'));\n```\n\n### Query modifiers.\n\n```php\n\u003c?php\n\nuse Doctrine\\ORM\\Query;\nuse FrankDeJonge\\DoctrineQuerySpecification\\QueryModifier;\n\nclass AsArray implements QueryModifier\n{\n    public function modifyQuery(Query $query, string $rootAlias): void\n    {\n        $query-\u003esetHydrationMode(Query::HYDRATE_ARRAY);\n    }\n}\n\n$publishedArticles = $articleRepository-\u003efindBySpecification(new AsArray);\n```\n\n### QueryBuilder modifiers.\n\n```php\n\u003c?php\n\nuse Doctrine\\ORM\\QueryBuilder;\nuse FrankDeJonge\\DoctrineQuerySpecification\\QueryBuilderModifier;\n\nclass InReverseOrder implements QueryBuilderModifier\n{\n    public function modifyQueryBuilder(QueryBuilder $builder, string $rootAlias): void \n    {\n        $builder-\u003eorderBy(\"{$rootAlias}.id\", \"DESC\");\n    }\n}\n\n$publishedArticles = $articleRepository-\u003efindBySpecification(new InReverseOrder);\n```\n\n## Specification composition\n\nThere are three ways of building compositions. Firstly there are specification collections\nwhich allow you to create `andX` and `orX` groups.\n\n```php\n$andSpecification = SpecificationCollection::all(\n    new IsPublished(),\n    new InReversedOrder(),\n    new WithAuthor(),\n);\n\n$orSpecification = SpecificationCollection::any(\n    new IsFeatured(),\n    new IsPublishedToday(),\n);\n```\n\nThe second way is to create new specification objects which encapsulate one of more other\nspecifications.\n\n```php\n\u003c?php\n\nuse Doctrine\\ORM\\QueryBuilder;\nuse FrankDeJonge\\DoctrineQuerySpecification\\QueryConstraint;\n\nclass FeaturedFromAuthor implements QueryConstraint\n{\n    public function __construct(Author $author)\n    {\n        $this-\u003eauthor = $author;\n    }\n\n    public function asQueryConstraint(QueryBuilder $queryBuilder, string $rootAlias): ?object\n    {\n        $expr = $queryBuilder-\u003eexpr();\n        \n        return $expr-\u003eandX(\n            (new FromAuthor($this-\u003eauthor))-\u003easQueryConstraint($queryBuilder, $rootAlias),\n            (new FeaturedArticle)-\u003easQueryConstraint($queryBuilder, $rootAlias),\n        );\n    }\n}\n```\n\nLastly you can extend a generic collection:\n\n```php\n\u003c?php\n\nuse FrankDeJonge\\DoctrineQuerySpecification\\SpecificationCollection\\All;\n\nclass FeaturedFromAuthor extends All\n{\n    public function __construct(Author $author)\n    {\n        parent::__construct(\n            new FromAuthor($author),\n            new FeaturedArticle(),\n        );\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrankdejonge%2Fdoctrine-query-specification","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrankdejonge%2Fdoctrine-query-specification","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrankdejonge%2Fdoctrine-query-specification/lists"}