DatastoreHasWhere Interface

The DatastoreHasWhere interface extends Datastore to add query-builder-style filtering. It provides the where() method, which returns a fluent query interface for building complex queries with multiple conditions, comparisons, and sorting.

This interface is for datastores that support rich querying beyond simple key-value filtering. If your storage supports SQL, this interface maps naturally to WHERE clauses.

Interface Definition

interface DatastoreHasWhere extends Datastore
{
    /**
     * Returns a query interface for building WHERE clauses.
     *
     * @return DatastoreWhereQuery A fluent query builder
     */
    public function where(): DatastoreWhereQuery;
}

Method

where(): DatastoreWhereQuery

Returns a query builder instance for constructing filtered queries.

Returns:

When to use:

Example: basic filtering

$posts = $postDatastore
    ->where()
    ->equals('author_id', 123)
    ->getResults();

Example: multiple conditions

$posts = $postDatastore
    ->where()
    ->equals('author_id', 123)
    ->greaterThan('published_date', '2024-01-01')
    ->lessThanOrEqual('published_date', '2024-12-31')
    ->getResults();

Example: OR conditions

$posts = $postDatastore
    ->where()
    ->equals('status', 'published')
    ->or()
    ->equals('status', 'featured')
    ->getResults();

Example: sorting and pagination

$posts = $postDatastore
    ->where()
    ->equals('author_id', 123)
    ->orderBy('published_date', 'DESC')
    ->limit(10)
    ->offset(20)
    ->getResults();

DatastoreWhereQuery API

The DatastoreWhereQuery interface provides a fluent API for building queries. Implementations typically support:

Comparison Methods

Logical Operators

Ordering and Pagination

Execution


Usage Patterns

Service Layer Queries

Services use where() for business queries:

final class PostService
{
    public function __construct(
        private PostDatastore $posts
    ) {}

    public function getRecentPublishedPosts(int $authorId, int $limit = 10): iterable
    {
        return $this->posts
            ->where()
            ->equals('author_id', $authorId)
            ->lessThanOrEqual('published_date', new DateTime())
            ->orderBy('published_date', 'DESC')
            ->limit($limit)
            ->getResults();
    }

    public function getPostsByTag(string $tag): iterable
    {
        return $this->posts
            ->where()
            ->like('tags', "%{$tag}%")
            ->getResults();
    }
}

Complex Filtering Example

// Find posts that are:
// - By author 123 OR author 456
// - Published in 2024
// - Status is "published" or "featured"
// - Sorted by views (descending)

$posts = $postDatastore
    ->where()
    ->in('author_id', [123, 456])
    ->greaterThanOrEqual('published_date', '2024-01-01')
    ->lessThan('published_date', '2025-01-01')
    ->in('status', ['published', 'featured'])
    ->orderBy('view_count', 'DESC')
    ->limit(20)
    ->getResults();

Counting with Queries

If your datastore also implements DatastoreHasCounts, you can count query results:

$query = $postDatastore
    ->where()
    ->equals('author_id', 123)
    ->greaterThan('published_date', '2024-01-01');

$count = $query->count(); // How many match?
$posts = $query->getResults(); // Fetch them

Deleting with Queries

Some implementations support delete() on queries:

// Delete all draft posts older than 30 days
$postDatastore
    ->where()
    ->equals('status', 'draft')
    ->lessThan('created_at', new DateTime('-30 days'))
    ->delete();

Note: Not all datastores support query-based deletion. Check your implementation.


Why This Interface Exists

The base get() method works for simple queries:

$posts = $datastore->get(['author_id' => 123]);

But it breaks down for:

DatastoreHasWhere solves this with a fluent, expressive API that maps cleanly to SQL and other query languages.

Relationship to Other Interfaces

vs. get()

Method Use Case Query Complexity
get(['key' => 'value']) Simple key-value filtering Low
where()->equals()->getResults() Complex queries with comparisons, OR logic, sorting High

Rule of thumb: If you can express it as ['key' => 'value'], use get(). Otherwise, use where().

Combining with Other Extensions

interface PostDatastore extends 
    Datastore,
    DatastoreHasPrimaryKey,
    DatastoreHasWhere,
    DatastoreHasCounts
{
    // get(), save(), delete()
    // find()
    // where()
    // count()
}

This gives you all query capabilities.


Implementation with Decorator Traits

Use WithDatastoreWhereDecorator to auto-implement:

final class PostDatastoreImpl implements DatastoreHasWhere
{
    use WithDatastoreWhereDecorator;

    public function __construct(
        private DatastoreHandlerHasWhere $handler
    ) {}
}

The trait delegates where() to the handler, which returns its query builder.


Implementation Notes

When implementing this interface:


When NOT to Implement This Interface

Skip DatastoreHasWhere if:


What's Next