Download the PHP package mediagone/doctrine-specifications without Composer
On this page you can find all versions of the php package mediagone/doctrine-specifications. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download mediagone/doctrine-specifications
More information about mediagone/doctrine-specifications
Files in mediagone/doctrine-specifications
Package doctrine-specifications
Short Description Doctrine implementation of repository Specifications pattern
License MIT
Informations about the package doctrine-specifications
Doctrine specifications
You probably already ended up with cluttered repositories or duplicated criteria, making it difficult to compose or maintain your queries.
But what if your queries were looking like this?
Or also:
If you like it, you probably need this package ;)
Combinations of criteria are unlimited, without any code duplication. \ It will also make hydration of custom read models (DTOs) a breeze.
Summary
- Introduction
- Example of usage
- Extended usages
- Return formats
- Joins
- Read models
- Using multiple Entity Managers
- Command bus
- Generic specifications
- Select specifications
- Filter specifications
- Additional specifications
- Debug specifications
- Naming and organizing specifications
Installation
This package requires PHP 7.4+ and Doctrine ORM 2.7+
Add it as Composer dependency:
Introduction
The classic Repository pattern (a single class per entity with several methods, one per query) quickly shows its limitations as it grows toward a big messy class.
Using Query Functions partially solves the problem by splitting up queries into separate classes, but you might still get a lot of code duplication. Things get worse if query criteria can be combined arbitrarily, which may result in the creation of an exponential number of classes.
The _Specifications pattern_ comes to the rescue helping you to split them into explicit and reusable filters, improving useability and testability of your database queries. This package is a customized flavor of this pattern (for purists), inspired by Benjamin Eberlei's article. It revolves around a simple concept: specifications. \ Each specification defines a set of criteria that will be automatically applied to Doctrine's QueryBuilder and Query objects, with the help of two methods:
Specifications can be freely combined to build complex queries, while remaining easily testable and maintainable separately.
Example of usage
We'll learn together how to create the following query:
Each method splits the query into separate "specifications":
- asEntity =>
SelectArticleEntity
specification - postedByUser =>
FilterArticlePostedBy
specification - orderedAlphabetically =>
OrderArticleAlphabetically
specification - maxCount =>
LimitMaxCount
specification
It will result in this clean query class:
We'll now explain step by step how to build this class:
SpecificationCompound class
First, we need to create our main class that will be updated later in our example. It extends SpecificationCompound
which provides a simple specification registration mechanism, we'll see that in details right after.
We'll use a static factory method asEntity()
to build our query object and define its return type. Here we want to get back results as entities, but we could hydrate instead a read model (DTO) (eg. asModel()
) or return a scalar value (eg. asCount()
).
Notes:
- Each
SpecificationCompound
must be initialized with a result format and (at least) one initial specification. - The compound's constructor is protected to enforce the usage of static factory methods, since descriptive naming is more meaningful about what the query will return.
- You may want to create another compound named
OneArticle
for queries that will always return a single result.
SelectArticleEntity specification
Our first specification defines the selected entity in our query builder by overloading the modifyBuilder
method:
Let's register it in our specification compound:
This is how we create a custom specification, but having to create a new class for each criterion is really cumbersome. Hopefully, the library provides many generic specifications you can reuse for common usages (see the Generic specifications section below).
So, we can replace our custom specification by the generic one:
Filtering specifications
Our second specification will filter articles by author:
Add it in the compound but this time using a fluent instance method:
Again, a generic specification exists, but this time you can use the following helper method to do that without using addSpecification()
(the method uses it internally):
Now we can do exactly the same for our two last filters: orderedAlphabetically
and maxCount
:
Execute the query
Finally, we can easily retrieve results according to our specification compound, by using the SpecificationRepository
class (which fully replaces traditional Doctrine repositories):
Notes:
- Use Dependency Injection (if available) to instantiate the
DoctrineSpecificationRepository
. - You can also use this service class as base to implement your own (eg. bus middlewares).
Extended usages
Return formats
The package allows results to get retrieved in different formats:
- MANY_OBJECTS : returns an array of hydrated objects (similar to
$query->getResult()
) - MANY_OBJECTS_AS_ITERABLE : returns an iterator over the query results (similar to
$query->toIterable()
) - SINGLE_OBJECT : returns a single hydrated object or null (similar to
$query->getOneOrNullResult()
) - SINGLE_SCALAR : returns a single scalar (similar to
$query->getSingleScalarResult()
)
Thereby, you can use the same specifications for different result types by adding multiple static factory methods in a compound.
Exemple of usage:
Joins
You can define query joins very easily by adding them in the static constructor. \
Note that the join will be applied to all your queries. \ But if you want to join only on demand, you can define it only for a given specification:
Joins using the same alias are only added once:
Read models
Retrieving data through dedicated classes, instead of entities might be very powerful (if we don't need to update the entity), because it speeds up complex queries (it limits the number of hydrated objects) and allows to flatten relations.
Let's take these two basic entities:
The normal way to get an Article with the name of it's category would be to query the entity and the related Category entity. But it leads to both objects hydration, and potentially multiple queries (depending on the fetch mode used).
Helpfully, Doctrine offers a way to hydrate custom classes by using the NEW operator (see official documentation).
Keeping in sync the query's selected fields and the DTO's constructor's parameters might be tedious, that's why the package also provides an interface to handle everything for you:
Selecting a Read Model in place of an Entity is very straightforward by registering a SelectReadModel
specification in the factory method:
The previous asModel
method translates to the following DQL:
Using multiple Entity Managers
By default, the default entity manager is used, but you can specify for each Compound which entity manager to use by overloading the getEntityManager
method:
You can also get it by the name used in the ORM configuration:
Command bus
Specification queries are best used through a Query bus, that suits very well with DDD, however it's not mandatory. You can easily tweak your own adapter for any bus or another kind of service.
Your query classes might extend SpecificationCompound
, making them automatically handleable by a dedicated bus middleware.
If you're looking for a bus package (or just want to see how it's done), you can use mediagone/cqrs-bus which proposes a SpecificationQuery
base class and the SpecificationQueryFetcher
middleware.
Generic specifications
To remove the hassle of creating custom specifications for most common usages, the library comes with built-in generic specifications. They can be easily registered using the specific compound's protected methods:
Select specifications
Specification name | Description |
---|---|
SelectEntity | Select and return the entity as query result. |
SelectReadModel | Select and return a DTO class as query result. |
SelectCount | Count and return the number of results of the query. |
JoinLeft | Declare a Left join. |
JoinInner | Declare an Inner join. |
GroupBy | Declare a GroupBy clause. |
Having | Declare a Hanving clause. |
Filter specifications
Specifications usable in criteria methods:
Compound method name | Specification name | QueryBuilder condition |
---|---|---|
->whereClause(...) | WhereClause | custom where clause |
->whereFieldDifferent(...) | WhereFieldDifferent | field != value |
->whereFieldEqual(...) | WhereFieldEqual | field = value |
->whereFieldGreater(...) | WhereFieldGreater | field > value |
->whereFieldGreaterOrEqual(...) | WhereFieldGreaterOrEqual | field >= value |
->whereFieldLesser(...) | WhereFieldLesser | field < value |
->whereFieldLesserOrEqual(...) | WhereFieldLesserOrEqual | field <= value |
->whereFieldIn(...) | WhereFieldIn | field IN (value) |
->whereFieldNotIn(...) | WhereFieldNotIn | field NOT IN (value) |
->whereFieldInArray(...) | WhereFieldInArray | field IN (values,generated,list) |
->whereFieldNotInArray(...) | WhereFieldNotInArray | field NOT IN (values,generated,list) |
->whereFieldIsNull(...) | WhereFieldIsNull | field IS NULL |
->whereFieldIsNotNull(...) | WhereFieldIsNotNull | field IS NOT NULL |
->whereFieldLike(...) | WhereFieldLike | field LIKE 'value' |
->whereFieldBetween(...) | WhereFieldBetween | field >= min AND field <= max |
->whereFieldBetweenExclusive(...) | WhereFieldBetweenExclusive | field > min AND field < max |
->orderResultsByAsc(...) | OrderResultsByAsc | ORDER BY expression ASC |
->orderResultsByDesc(...) | OrderResultsByDesc | ORDER BY expression DESC |
Example of usage:
Additional specifications
Compound method name | Specification name | Note |
---|---|---|
->setParameter(...) | SetParameter | Define a query builder parameter. |
->limitResultsOffset(...) | LimitResultsOffset | (Pagination) Defines how many results to skip. |
->limitResultsMaxCount(...) | LimitResultsMaxCount | (Pagination) Defines the (max) number of returned results. |
->limitResultsPaginate(...) | LimitResultsPaginate | (Pagination) Combines MaxCount and Offset effects, with different parameters. |
Exemple of usage:
A last couple of specifications provide even more flexibility by allowing you to modify the Doctrine QueryBuilder/Query without having to create separate classes:
Compound method name | Specification name |
---|---|
->modifyBuilder(...) | ModifyBuilder |
->modifyQuery(...) | ModifyQuery |
Debugging specifications
The SpecificationCompound
class comes with built-in methods that adds debug oriented specifications to all compound classes, you don't have to include them in your own compounds:
Compound method name | Specification name |
---|---|
->dumpDQL(...) | DebugDumpDQL |
->dumpSQL(...) | DebugDumpSQL |
So you can easily dump the generated DQL and SQL with few method calls:
Organizing specifications
Naming specifications
Naming convention used in this exemple is only a suggestion, feel free to adapt to your needs or preferences.
There is no hard requirement about naming, but you should use defined prefixes to differentiate between your specifications:
- Filter... : specifications that filter out results, but allowing multiple results.
- Get... : specifications that filter out results, in order to get a unique (or null) result.
- Order... : specifications that change the results order.
- Select... : specifications that define selected result data (entities, DTO, joins, groupBy...) ...
Files organization
You'll probably want to create a separate compound for querying single article (eg. OneArticle
) since the specification filters are usually not the same for single or array results (shared specifications can be easily added to both compounds).
Hence a suggested file structure might be:
License
Doctrine Specifications is licensed under MIT license. See LICENSE file.
All versions of doctrine-specifications with dependencies
doctrine/orm Version ^2.7|^3.0
symfony/var-dumper Version ^5.1|^6.0|^7.0