What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Query Object pattern represents database query criteria as an object, so callers can express and combine searches without requiring a separate finder method for every variation. In PHP, a practical design is to pass a criteria object such as OrderQuery to a repository or query service that translates it into parameterized SQL. The criteria object describes the request; it does not have to execute the query.

What is the Query Object pattern?

Martin Fowler defines a Query Object as “an object that represents a database query.” He describes it as an interpreter: a structure of objects that can form itself into SQL. Depending on the design, the object can express domain concepts such as an order’s status or customer rather than exposing table and column names. Fowler’s Query Object catalog entry dates to 5 March 2003.

The pattern addresses two related problems: specialized finder methods make new, one-off combinations awkward, and duplicated SQL spreads the effects of schema changes across the application. A shared translation boundary can make those changes easier to locate, but the pattern alone neither guarantees database independence nor removes the need to map criteria to a specific persistence mechanism.

How do I use the Query Object pattern in PHP?

One restrained PHP adaptation is to make the query object a value-like description of the search, then let a repository or query service build and execute the SQL. This is an implementation choice, not an official or canonical PHP version of the pattern. PHP classes can be instantiated with new; the key design decision is which component owns translation and execution.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

1. Define explicit criteria

For example, an OrderQuery could carry optional status, customer ID, and date-range criteria. Keep the inputs deliberate: typed constructor parameters or typed properties make the accepted search dimensions visible, while controlled mutation or immutability helps callers reason about a query after creating it.

2. Translate criteria at the persistence boundary

A repository method can map those properties to SQL and bound parameters. The following is illustrative pseudocode; it shows the responsibility split rather than a complete database implementation:

$query = new OrderQuery(status: 'paid', customerId: 42);
$orders = $orderRepository->search($query);

Inside search(), the repository can add only the predicates whose criteria are present, bind their values, execute the statement, and return an order collection or iterator. Parameter binding is part of implementing the SQL boundary; the Query Object pattern itself does not provide a security guarantee.

3. Keep caller vocabulary useful

Callers should express the search in terms that make sense to the application, such as “paid orders for this customer,” when that abstraction is valuable. The translator remains responsible for relating those terms to the actual schema. If callers instead construct table-specific fragments, they lose much of the opportunity to localize persistence changes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Separate reads from writes when it helps

Search methods are queries: they return results without changing observable state. Operations that change state are commands and can live in separate methods. This command-query separation is a useful organizing principle, not an absolute rule; Fowler notes exceptions in his Command Query Separation discussion (5 December 2005).

Query Object, finder methods, query builders, and Repository

These terms describe different responsibilities, so choose the comparison that matches the design decision you are making.

Approach What it represents or does Flexibility and trade-off
Finder methods Named operations such as findPaidOrdersForCustomer(). Clear for a small, fixed set of lookups, but combinations can lead to many specialized methods.
Query Object A structured representation of criteria, such as an OrderQuery. Callers can combine supported criteria without needing a new finder for every combination; it adds an abstraction that must be maintained.
Query builder An API for incrementally constructing a query, often with persistence-specific concepts. Can be flexible, but may expose SQL or schema details. A Query Object can be translated by a builder; they are not mutually exclusive.
Repository A collection-like interface between the domain and data-mapping layers, through which clients access domain objects and may submit declarative query specifications. Provides a domain-facing access boundary. A Query Object may be one kind of specification accepted by a Repository.

Fowler’s Repository catalog entry describes the collection-like role and notes its value in complex domain models, systems with many domain classes, or applications with heavy querying, where concentrating query construction can reduce duplication. The Repository provides access; the Query Object describes criteria. One can be used without the other.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Where should the query object live?

Put the criteria type where application callers can use it without depending on SQL, and keep schema-aware translation in the persistence layer. A typical flow is: application code creates OrderQuery, passes it to an order repository or query service, and receives results. This avoids requiring each caller to know how orders are stored.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The object can reside alongside the relevant application or domain query types, while the SQL mapping belongs with the repository or persistence adapter. The right boundary depends on the application: the essential point is not a particular folder name, but that criteria description and database-specific translation remain distinct when that separation is useful.

When should I use a Query Object?

Introduce one when query variation or duplication justifies the extra type. It is most useful when multiple callers need overlapping combinations of criteria, ad hoc searches are becoming difficult to serve with fixed finder names, or query construction is repeated enough that schema changes require edits in several places.

  • Good fit: several meaningful criteria can be combined, and callers benefit from expressing those combinations consistently.
  • Good fit: persistence mapping is duplicated and you want one boundary to translate domain-level criteria.
  • Probably unnecessary: there is one stable lookup, used in one place, and a clear finder method is sufficient.
  • Reconsider the design: the object merely wraps a single hard-coded query, or callers must manipulate low-level SQL details anyway.

The PHP pattern examples project emphasizes choosing patterns for a reason rather than applying them mechanically; its DesignPatternsPHP project is useful context for treating patterns as trade-offs, not required ceremony.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.