PHPackages                             rasuvaeff/specification - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. rasuvaeff/specification

ActiveLibrary[Search &amp; Filtering](/categories/search)

rasuvaeff/specification
=======================

Type-safe specification pattern for composing Yiisoft DB queries: AND, OR, NOT, comparisons, IN, BETWEEN, LIKE — with SQL injection protection.

v1.0.2(3w ago)00BSD-3-ClausePHPPHP 8.3 - 8.5CI passing

Since May 31Pushed 3w agoCompare

[ Source](https://github.com/rasuvaeff/specification)[ Packagist](https://packagist.org/packages/rasuvaeff/specification)[ Docs](https://github.com/rasuvaeff/specification)[ RSS](/packages/rasuvaeff-specification/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (1)Dependencies (26)Versions (4)Used By (0)

rasuvaeff/specification
=======================

[](#rasuvaeffspecification)

[![Latest Stable Version](https://camo.githubusercontent.com/d6921d03961694062eb4a68c3db826adfd52fda721bea20e19e16c537d183795/68747470733a2f2f706f7365722e707567782e6f72672f7261737576616566662f73706563696669636174696f6e2f76)](https://packagist.org/packages/rasuvaeff/specification)[![Total Downloads](https://camo.githubusercontent.com/ec96c4fa79217597f6cb5dcd0306e110d0624977b26e9fe57cf816328a1d41a4/68747470733a2f2f706f7365722e707567782e6f72672f7261737576616566662f73706563696669636174696f6e2f646f776e6c6f616473)](https://packagist.org/packages/rasuvaeff/specification)[![Build](https://github.com/rasuvaeff/specification/actions/workflows/build.yml/badge.svg)](https://github.com/rasuvaeff/specification/actions/workflows/build.yml)[![Static analysis](https://github.com/rasuvaeff/specification/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/rasuvaeff/specification/actions/workflows/static-analysis.yml)[![Psalm level](https://camo.githubusercontent.com/68f7f31799f2b93c710b14ba3877072e7fe07ec9d7cee3fdf67e14beab3e1b6f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7073616c6d2d6c6576656c5f312d626c75652e737667)](https://github.com/rasuvaeff/specification/actions/workflows/static-analysis.yml)[![PHP](https://camo.githubusercontent.com/5a4057a094d9a3e57870b2996365793dc6919a175c20ca9e39af3140720603af/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f7261737576616566662f73706563696669636174696f6e2f706870)](https://packagist.org/packages/rasuvaeff/specification)[![License](https://camo.githubusercontent.com/6cb285b57819f8de0acfb34923298f4f569f962544e8fe35331da2d163f4e485/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4253442d2d332d2d436c617573652d626c75652e737667)](LICENSE.md)

Specification pattern for building [Yiisoft DB](https://github.com/yiisoft/db) queries.

```
use Rasuvaeff\Specification\SpecificationBuilder;
use Rasuvaeff\Specification\QueryApplier;

$spec = SpecificationBuilder::create()
    ->whereEqual('status', 'active')
    ->whereGreaterThan('age', 18)
    ->whereIn('role', ['admin', 'moderator'])
    ->orderBy(['created_at' => 'DESC'])
    ->limit(20)
    ->build();

$query = (new \Yiisoft\Db\Query\Query($db))->from('users');
QueryApplier::apply($spec, $query);
$rows = $query->all();
```

> **Using an AI coding assistant?** [`llms.txt`](llms.txt) is a compact, self-contained reference of the whole public API plus copy-paste recipes — drop it into the model's context. Contributors: see [`AGENTS.md`](AGENTS.md).

Requirements
------------

[](#requirements)

- PHP 8.3+
- `yiisoft/db` ^2.0.1

Installation
------------

[](#installation)

```
composer require rasuvaeff/specification

```

Usage
-----

[](#usage)

### SpecificationBuilder

[](#specificationbuilder)

Fluent builder for composing query conditions:

```
use Rasuvaeff\Specification\SpecificationBuilder;
use Rasuvaeff\Specification\QueryApplier;

$spec = SpecificationBuilder::create()
    ->whereEqual('status', 'active')
    ->whereGreaterThan('age', 18)
    ->whereNull('deleted_at')
    ->build();

$query = (new Yiisoft\Db\Query\Query($db))->from('users');
QueryApplier::apply($spec, $query);
$rows = $query->all();
```

Available methods:

MethodSQL equivalent`where($col, $val, $op)``col op val` (any operator)`whereEqual($col, $val)``col = val``whereNotEqual($col, $val)``col != val``whereGreaterThan($col, $val)``col > val``whereGreaterThanOrEqual($col, $val)``col >= val``whereLessThan($col, $val)``col < val``whereLessThanOrEqual($col, $val)``col withComparison('status', 'active')
    ->withComparison('age', 18, '>')
    ->withOrderBy(['created_at' => 'DESC'])
    ->withLimit(20)
    ->withOffset(40);

// OR condition arrays via OrConditionSpecification.
$orConditionSpec = CompositeSpecification::create()
    ->withOrCondition(['status' => 'active', 'type' => 'pending']);

// OR conditions
$orSpec = OrSpecification::create(
    ComparisonSpecification::equal('type', 'admin'),
    ComparisonSpecification::equal('type', 'moderator'),
);

// NOT condition
$notSpec = new NotSpecification(
    new ComparisonSpecification('status', 'banned'),
);

// Raw SQL — see the Security note below
$rawSpec = new RawSpecification('age > :age', ['age' => 18]);

// Offset for pagination
$offset = CompositeSpecification::create()
    ->withLimit(10)
    ->withOffset(20);

// Raw SQL — see the Security note below
$rawComposite = CompositeSpecification::create()
    ->withRaw('price > :min AND price < :max', ['min' => 10, 'max' => 100]);
```

### ComparisonSpecification factory methods

[](#comparisonspecification-factory-methods)

```
ComparisonSpecification::equal('col', $val)
ComparisonSpecification::notEqual('col', $val)
ComparisonSpecification::greaterThan('col', $val)
ComparisonSpecification::greaterThanOrEqual('col', $val)
ComparisonSpecification::lessThan('col', $val)
ComparisonSpecification::lessThanOrEqual('col', $val)
ComparisonSpecification::like('col', 'pattern')
ComparisonSpecification::notLike('col', 'pattern')
ComparisonSpecification::ilike('col', 'pattern')
ComparisonSpecification::notIlike('col', 'pattern')
ComparisonSpecification::startsWith('col', 'prefix')
ComparisonSpecification::endsWith('col', 'suffix')
ComparisonSpecification::contains('col', 'substring')
ComparisonSpecification::in('col', [1, 2, 3])
ComparisonSpecification::notIn('col', [4, 5, 6])
ComparisonSpecification::between('col', $from, $to)
ComparisonSpecification::notBetween('col', $from, $to)
ComparisonSpecification::isNull('col')
ComparisonSpecification::isNotNull('col')
```

### Custom visitor

[](#custom-visitor)

Implement `SpecificationVisitor` to traverse the specification tree:

```
use Rasuvaeff\Specification\SpecificationVisitor;
use Rasuvaeff\Specification\ComparisonSpecification;
// ... other specification imports

/** @implements SpecificationVisitor */
final class CountingVisitor implements SpecificationVisitor
{
    private int $count = 0;

    #[\Override]
    public function visitComparison(ComparisonSpecification $specification): int
    {
        return ++$this->count;
    }

    // ... implement all visit* methods (visitComparison, visitComposite, visitNot,
    //     visitOr, visitOrCondition, visitRaw, visitOrderBy, visitLimit, visitOffset)
}
```

Examples
--------

[](#examples)

Runnable, offline examples (in-memory SQLite) live in [`examples/`](examples/): `builder.php` (AND/IN/BETWEEN) and `or-not-raw.php` (OR/NOT/raw/order+limit).

```
composer install && php examples/builder.php
```

Security
--------

[](#security)

- **Values are parameterized.** All comparison/IN/BETWEEN/LIKE values are bound as parameters by `yiisoft/db`, so they are safe against SQL injection.
- **Column names are not validated** — they are passed to `yiisoft/db` and quoted as identifiers, but there is no allow-list. Pass only **trusted** column names (typically hard-coded), never raw user input.
- **`RawSpecification` is a raw escape hatch.** The condition string is **not**escaped — never build it from untrusted input. Pass user values only through the `$params` map (placeholders): `new RawSpecification('age > :age', ['age' => $value])`.

Performance
-----------

[](#performance)

`SpecificationBuilder` is immutable — each `where*()`, `limit()`, and `offset()` call clones the builder before returning. This is safe and predictable but carries a small overhead (~3.3µs for a 7-step chain, vs ~2.4µs for direct `CompositeSpecification`composition). `orWhere()` additionally allocates a temporary builder and invokes a closure (~2.8µs vs ~1.4µs for direct `OrSpecification::create()`).

For most web request workloads (1–5 specs per request, DB queries taking 1–100ms) this overhead is negligible. For **high-throughput batch processing** where specs are built in a tight loop, prefer the direct `CompositeSpecification` API:

```
// ~26% faster than SpecificationBuilder for a 7-condition chain
$spec = CompositeSpecification::create()
    ->withComparison('status', 'active')
    ->withComparison('age', 18, '>')
    ->withComparison('role', ['admin', 'editor'], 'in')
    ->withLimit(100);

// ~48% faster than orWhere() for OR composition
$spec = CompositeSpecification::create()
    ->withSpecification(OrSpecification::create(
        CompositeSpecification::create()->withComparison('status', 'active'),
        CompositeSpecification::create()->withComparison('status', 'pending'),
    ));
```

Benchmarks live in `benchmarks/` and run via `composer bench` (requires [testo/bench](https://github.com/php-testo/testo)).

Notes
-----

[](#notes)

- `ilike` / `not ilike` are PostgreSQL-specific; other drivers (e.g. MySQL) do not support them. Use `like` for case-insensitive needs on those drivers.
- For OR conditions use `OrSpecification` or `SpecificationBuilder::orWhere()`. `CompositeSpecification` composes with **AND** semantics.
- `withOrCondition()` value formats: a scalar is plain equality (`'status' => 'active'`); an array whose first element is a known operator is a shorthand (`'age' => ['>', 18]`, `'type' => ['in', ['a', 'b']]`); any other array is treated as a value, so a plain list (`'name' => ['a', 'b']`) becomes an `IN` condition. The operator is matched case-insensitively.

License
-------

[](#license)

BSD-3-Clause.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance95

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Total

3

Last Release

25d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.0.1PHP 8.3 - 8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/b0812d5572a7041dfe36e222d295b2e6dc55833a605350fcde58a51a5965ed30?d=identicon)[rasuvaeff](/maintainers/rasuvaeff)

---

Top Contributors

[![rasuvaeff](https://avatars.githubusercontent.com/u/1352718?v=4)](https://github.com/rasuvaeff "rasuvaeff (21 commits)")

---

Tags

criteriafilterphpquery-builderspecificationvisitor-patternyiiyii3yiisoftspecificationqueryfilterdbyiiquery builderpatterncriteriayii3yiisoft

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/rasuvaeff-specification/health.svg)

```
[![Health](https://phpackages.com/badges/rasuvaeff-specification/health.svg)](https://phpackages.com/packages/rasuvaeff-specification)
```

###  Alternatives

[webmozart/expression

Implementation of the Specification pattern and logical expressions for PHP.

215398.3k11](/packages/webmozart-expression)[mehradsadeghi/laravel-filter-querystring

Filter your queries based on url query string parameters like a breeze.

168128.5k](/packages/mehradsadeghi-laravel-filter-querystring)[ajcastro/searchable

Pattern-matching search for Laravel eloquent models.

2854.9k](/packages/ajcastro-searchable)[ambengers/query-filter

Laravel package for filtering resources with request query string

3513.9k](/packages/ambengers-query-filter)[clue/json-query

JSON query language filter in PHP

3015.9k](/packages/clue-json-query)[hashemi/queryfilter

A simple &amp; dynamic package for your eloquent query in laravel. It will help you to write query logic individual for each parameter.

391.2k](/packages/hashemi-queryfilter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
