PHPackages                             beatswitch/distil - 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. beatswitch/distil

ActiveLibrary

beatswitch/distil
=================

0.2.0(6y ago)165.9k[2 issues](https://github.com/remipelhate/distil/issues)[1 PRs](https://github.com/remipelhate/distil/pulls)MITPHPPHP ^7.1

Since Sep 7Pushed 5y ago1 watchersCompare

[ Source](https://github.com/remipelhate/distil)[ Packagist](https://packagist.org/packages/beatswitch/distil)[ RSS](/packages/beatswitch-distil/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (1)Dependencies (1)Versions (7)Used By (0)

[![](https://camo.githubusercontent.com/0aaf98ee6b8c7555fb68c3f2b8963b9cb886a802be14aa99ff5fccd212563bfb/68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6173736574732e626561747377697463682e636f6d2f64697374696c2d6c6f676f2e737667)](https://camo.githubusercontent.com/0aaf98ee6b8c7555fb68c3f2b8963b9cb886a802be14aa99ff5fccd212563bfb/68747470733a2f2f73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f6173736574732e626561747377697463682e636f6d2f64697374696c2d6c6f676f2e737667)

 [![Build Status](https://camo.githubusercontent.com/301722b95a8b619696479fcd5ebee7b5258812ef16ebb8514ee273497252faa8/68747470733a2f2f696d672e736869656c64732e696f2f7472617669732f72656d6970656c686174652f64697374696c2f6d61737465722e7376673f7374796c653d666c61742d737175617265)](https://travis-ci.org/remipelhate/distil) [![StyleCI](https://camo.githubusercontent.com/2073c81caa98eac06801e160a392981f6b6ffebd13902d59645653dedbb3af70/68747470733a2f2f7374796c6563692e696f2f7265706f732f3130323131373233312f736869656c64)](https://styleci.io/repos/102117231/shield) [![Licence](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](license.md)

Distil provides a simple API to describe a variable number of conditions that should be met by a result set in PHP. This, for example, makes dynamically constructing queries through small value objects a breeze.

- [Getting Started](#getting-started)
    - [Simple Example](#simple-example)
    - [Requirements](#requirements)
    - [Installation](#installation)
- [Concepts](#concepts)
    - [Criteria](#criteria)
    - [Criterion](#criterion)
    - [Keywords](#keywords)
    - [Common Criteria](#common-criteria)
    - [Factories](#factories)
- [Contributing](#contributing)
- [Changelog](#changelog)
- [License](#license)

Getting Started
===============

[](#getting-started)

Simple Example
--------------

[](#simple-example)

Let's consider the following scenario: you have a query object `GetPosts` that returns all posts of your blog by default. However, you only need to retrieve posts from a specific author on some places. The query itself is identical, you only need to strip out the ones that were created by anyone other than the given author. To do so, the query object can accept criteria that may or may not hold the "author" criterion:

```
$authorId = 369;

// Author is an implementation of Distil\Criterion which acts as a Criteria factory.
$criteria = Author::criteria($authorId);

$posts = $getPostsQuery->get($criteria);
```

The query object internals can then append statements to the query depending of the given criteria. Distil only describes the criteria, you can interpret them however you want. You're fully in control:

```
use Distil\Criteria;

final class GetPosts
{
    public function get(Criteria $criteria)
    {
        ...

        if ($criteria->has('author')) {
            $query->where('creator_id', '=', $criteria->get('author')->value());
        }
    }
}
```

This approach comes in handy when you need to pass along a variable number of criteria to a single query. Imagine we'd use that `GetPosts` query object to expose those posts through an API and allow users to filter them by author, publish\_date, ... and eventually sort them differently:

```
$criteria = new Distil\Criteria();

if (isset($requestData['author'])) {
    $criteria->add(Author::fromString($requestData['author']));
}

if (isset($requestData['sort'])) {
    $criteria->add(new Sort($requestData['sort']));
}

...

$posts = $getPostsQuery->get($criteria);
```

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

[](#requirements)

- PHP &gt;= 7.1

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

[](#installation)

You can install this package through Composer:

```
$ composer require remipelhate/distil

```

Concepts
========

[](#concepts)

Criteria
--------

[](#criteria)

*Criteria* is a collection of conditionals that describe which records should be included within a result set (filtering, limiting, ...) or how it should be presented (e.g. sorting). Distil represents these single conditionals within the `Distil\Criteria` collection as `Distil\Criterion` instances (see next chapter).

### Adding Criteria

[](#adding-criteria)

Let's reconsider the example at the top of the docs. Imagine we'd want to retrieve all posts from author with ID 1 and sort them in ascending order of the publish\_date. You can pass along Criterion instances on construction:

```
$criteria = new Distil\Criteria(new Author($authorId), new Sort('publish_date'));
```

… or fluently through the `add()` method:

```
$criteria = new Distil\Criteria();

$criteria->add(new Author($authorId))
    ->add(new Sort('publish_date'));
```

**Each Criterion instance within the collection is unique by name.** `add()` does not allow to overwrite an instance in the collection with another one carrying the same name. If you event need to overwrite one, you can use the `set()` method:

```
$criteria = new Distil\Criteria(new Author(1));

$criteria->add(new Author(2)); // This will throw an Exception.
$criteria->set(new Author(2)); // This will overwrite new Author(1) with new Author(2)
```

### Getting Criteria

[](#getting-criteria)

You can check if it contains a Criterion instance by name:

```
$criteria = new Distil\Criteria(new Author(1));

$criteria->has('published'); // returns false
$criteria->has('author'); // returns true
```

... and get it:

```
$criteria = new Distil\Criteria(new Author(1));

$criteria->get('published'); // returns null
$criteria->get('author'); // returns the Author instance
```

### Array Access

[](#array-access)

`Distil\Criteria` implements PHP's `ArrayAccess` interface, meaning you can interact with it as an array:

```
$criteria[] = new Author(1); // Acts as set()
$author = $criteria['author']; // Acts as get(), but throws an error if the name doesn't exist
```

Criterion
---------

[](#criterion)

A *Criterion* is a single condition to which a result set should adhere. Think of a filter, a limit, sorting, ... Distil represents these as small value objects (identified by a unique name) that wrap around a single value. Those objects must implement the `Distil\Criterion` interface.

### Example

[](#example)

Once more, let's go back to our example at the top of the docs and create that `author` filter for the query object. This filter holds the ID of an author:

```
use Distil\Criterion;

final class Author implements Criterion
{
    const NAME = 'author';

    public function __construct(int $id)
    {
        $this->id = $id;
    }

    public function name(): string
    {
        return self::NAME;
    }

    public function value(): int
    {
        return $this->id;
    }
}
```

### Typed Criteria

[](#typed-criteria)

Distil ships with a set of strictly typed abstract classes that you can use to add some default behaviour to your Criterion implementations:

- **Distil\\Types\\BooleanCriterion** - Wraps around a boolean value.
- **Distil\\Types\\DateTimeCriterion** - Wraps around an instance of PHP's `DateTimeInterface` and optionally accepts a datetime format.
- **Distil\\Types\\IntegerCriterion** - Wraps around a integer value.
- **Distil\\Types\\ListCriterion** - Wraps around an array value.
- **Distil\\Types\\StringCriterion** - Wraps around a string value.

Each of these can:

- be constructed from a string value through the `fromString` named constructor (remember, the default constructor of these are all strictly typed). This is particularly useful when instantiating `Criterion` instances from a URI query string value.
- be constructed from string keywords (see [Keywords](#keywords)).
- be casted to a string.
- act as a `Distil\Criteria` factory (see [Criteria Factories](#criteria-factories)).

So, we can simplify our `Author` filter from above as such:

```
use Distil\Types\IntegerCriterion;

final class Author extends IntegerCriterion
{
    const NAME = 'author';

    public function name(): string
    {
        return self::NAME;
    }
}
```

Keywords
--------

[](#keywords)

When creating a `Distil\Criterion` from a string, you can't always cast that string value to the appropriate value type. Therefor, Distil allows you to define keywords for some specific values.

### Working with keyword values

[](#working-with-keyword-values)

Let's illustrate this with an example and create a `Limit` filter. The value contained by this filter should either be an integer or `null` (aka, there is no limit):

```
use Distil\Criterion;

final class Limit implements Criterion
{
    public function __construct(?int $value)
    {
        $this->value = $value;
    }

    public static function fromString(string $value): self
    {
        return new self((int) $value);
    }

    public function name(): string
    {
        return 'limit';
    }

    public function value(): ?int
    {
        return $this->value;
    }
}
```

If we were to offer this as a filter on, for example, a public API, we'd want to be able to resolve `limit=unlimited` to `new Limit(null)`. 'unlimited' can't be naturally casted to a `null` value, so we need to register it as a keyword. To do so, our limit Criterion needs to implement the `Distil\Keywords\HasKeywords` interface, which requires you to define a `keywords()` method:

```
use Distil\Criterion;
use Distil\Keywords\HasKeywords;
use Distil\Keywords\Keyword;

final class Limit implements Criterion, HasKeywords
{
    ...

    public static function fromString(string $value): self
    {
        $value = (new Keyword(self::class, $value))->value();

        return new self($value ? (int) $value : $value);
    }

    public static function keywords(): array
    {
        return ['unlimited' => null];
    }
}
```

Note the usage of the `Distil\Keywords\Keyword` class in our `fromString()` constructor. It's a small value object that accepts the Criterion class name and the given string value on construction. Using the `keywords()` method, it can check whether or not the given string value is a keyword for another value. If it is, the actual value will be returned:

```
new Limit(null) == Limit::fromString('unlimited'); // true
```

Now, imagine you'd want to be able to cast the Criterion value back to a string. Using the `Distil\Keywords\Value` class, you can easily derive the keyword for a value:

```
use Distil\Criterion;
use Distil\Keywords\HasKeywords;
use Distil\Keywords\Keyword;

final class Limit implements Criterion, HasKeywords
{
    ...

    public static function keywords(): array
    {
        return ['unlimited' => null];
    }

    public function __toString(): string
    {
        return (new Value($this, $this->value))->keyword() ?: (string) $this->value;
    }
}
```

If the value is associated with a keyword, the keyword will be returned. If it isn't, we'll get `null`:

```
(string) Limit::fromString('unlimited') === 'unlimited'; // true
```

> **Note**: Limit was simply used here as an example. It's actually available out of the box. Make sure to check out the [Common Criteria](#common-criteria) section.

### Keywords on Typed Criteria

[](#keywords-on-typed-criteria)

As mentioned in the [Typed Criteria](#typed-criteria) section, any Criterion instance that extends either one of the `Distil\Types` automatically handles keywords when created from or casted to a string. This means you only need to implement the `Distil\Keywords\HasKeywords` interface without having to overwrite the `fromString()` or `__toString()` methods.

In addition, any instances of `Distil\Types\BooleanCriterion` will automatically handle 'true' and 'false' string values.

Common Criteria
---------------

[](#common-criteria)

Distil provides a couple of common criteria out of the box:

### Limit

[](#limit)

`Distil\Common\Limit` is Criterion implementation that wraps around an integer or null value. It does not extend a Criterion Type, but has the same capabilities as any of them:

- It can be constructed from a string value through the `fromString` named constructor.
- When constructed from a string value, it accepts the "unlimited" keyword (which is mapped to `null`).
- It can be casted to a string.
- It can be used as a Criteria factory.

In addition, Limit has a default value (being 10). So you can instantiate it without any arguments:

```
$limit = new Distil\Common\Limit();

$limit->value(); // Returns 10, its default value
```

### Sort

[](#sort)

`Distil\Common\Sort` is an extension of `Distil\Types\ListCriterion`. It accepts a list of field or properties by which a result set should be sorted:

```
$sort = new Distil\Common\Sort('-name', 'created_at');
```

Note the usage of the "-" sign in front of the "name" property to indicate the result set should be sorted in descending order on that property.

Its `value()` method simply returns an array containing those sort fields, but you can also retrieve them as small `Distil\Common\SortField` value objects:

```
use Distil\Common\SortField;

$sort = new Distil\Common\Sort('-name');

$sort->sortFields() == [new SortField('name', SortField::DESC)];
```

Factories
---------

[](#factories)

### Criteria Factories

[](#criteria-factories)

Any Criterion instance that extends either one of the `Distil\Types` can also be used as `Distil\Criteria` factories:

```
new Distil\Criteria(new Author($authorId));

// can be rewritten as...

Author::criteria($authorId);
```

Under the hood, this named constructor will delegate its arguments to the Criterion's default constructor, and pass itself along a new `Distil\Criteria` instance.

If you want to enable using a Criterion as Criteria factory without extending any of the available Criterion Types, you can use the `Distil\ActsAsCriteriaFactory` trait.

### CriterionFactory

[](#criterionfactory)

In some cases, you may want to instantiate a Criterion by name. Take the following snippet from the example at the top of the docs:

```
$criteria = new Distil\Criteria();

if (isset($requestData['sort'])) {
    $criteria->add(new Sort($requestData['sort']));
}

if (isset($requestData['limit'])) {
    $criteria->add(Limit::fromString($requestData['limit']));
}

...
```

Rather than doing this in every controller that may use the Sort and Limit criteria, you can register a resolvers for them in the `Distil\CriterionFactory`. A resolver is either a class name or a callable (e.g. named constructor, anonumous function, ...) that actually instantiates the Criterion:

```
// Note that these resolvers could be injected into the factory through your IoC container.
$factory = new Distil\CriterionFactory([
    'sort' => Distil\Common\Sort::class,
    'limit' => Distil\Common\Limit::class.'::fromString',
    'foo' => function () {
        // Some logic to resolve the foo criterion...
    },
]);
$criteria = new Distil\Criteria();

foreach ($requestData as $name => $value) {
    $criteria->add($factory->createByName($name, $value)),
}
```

Contributing
------------

[](#contributing)

Please see [the contributing file](CONTRIBUTING.md) for details.

Changelog
---------

[](#changelog)

You can see a list of changes for each release in [our changelog file](CHANGELOG.md).

License
-------

[](#license)

The MIT License. Please see [the license file](LICENSE) for more information.

###  Health Score

29

—

LowBetter than 59% of packages

Maintenance18

Infrequent updates — may be unmaintained

Popularity25

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 56.4% 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 ~395 days

Total

3

Last Release

2383d ago

### Community

Maintainers

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

---

Top Contributors

[![remipelhate](https://avatars.githubusercontent.com/u/3226002?v=4)](https://github.com/remipelhate "remipelhate (22 commits)")[![driesvints](https://avatars.githubusercontent.com/u/594614?v=4)](https://github.com/driesvints "driesvints (17 commits)")

---

Tags

criteriaphp

### Embed Badge

![Health badge](/badges/beatswitch-distil/health.svg)

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

PHPackages © 2026

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