Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Criteria Laravel Package

complex-heart/criteria

Bring the Criteria pattern to Laravel Eloquent with complex-heart/criteria. Encapsulate query constraints in reusable, composable classes so filtering stays clean, testable, and out of your models. Ideal for flexible repository queries and complex search logic.

View on GitHub
Deep Wiki
Context7

Getting Started

complex-heart/criteria is a small, framework-agnostic PHP library that gives you value objects for the Criteria pattern: a Criteria describes what to fetch (filters, ordering, pagination) while your repository decides how. There is no service provider, no config file, and no migrations — it is plain PHP with a Composer autoload, so installation is the whole setup:

composer require complex-heart/criteria

Look here first, in this order:

  1. composer.json — confirm the PSR-4 namespace (expect ComplexHeart\...) so you know the exact FQCNs.
  2. src/ — read the Criteria class/interface first, then whatever represents filters/operators, ordering, and pagination. With 5 stars and zero dependents there is effectively no external documentation; the source is the spec.
  3. tests/ (if present) — the fastest way to learn intended usage is the assertions, not the README.

First use case: collapse a widening repository signature into one that takes a Criteria.

// Before: every new filter breaks the interface and every implementation
$repo->findActiveByTenant($tenantId, $sort, $page, $perPage);

// After: the interface is stable forever
$repo->findByCriteria(
    Criteria::create()
        ->withFilter('tenant_id', '=', $tenantId)
        ->withFilter('status', '=', 'active')
        ->orderBy('created_at', 'desc')
        ->paginate(page: 1, perPage: 25)
);

(Class and method names above are illustrative — mirror whatever src/ actually exposes. Verify the exact factory names before writing them into a dozen call sites.)

Implementation Patterns

Criteria as named domain concepts, not ad-hoc filters. The single biggest payoff is replacing query-shaped code in controllers with intention-revealing constructors: Criteria::forActiveSubscriptions(), Criteria::forOverdueInvoices($asOf). Callers stop knowing column names, and the query becomes unit-testable without a database.

One adapter per persistence technology. Keep the package DB-agnostic and write a translator in your infrastructure layer:

final class EloquentCriteriaApplier
{
    public function apply(Builder $query, Criteria $criteria): Builder { /* ... */ }
}

Everything persistence-specific — column mapping, LIKE wildcard wrapping, IN expansion — lives here. That adapter is also the only place you need to touch if you ever drop the package.

Decorator for mandatory scoping. Wrap your repository so tenant/authorization filters are injected before the caller's criteria:

final class TenantScopedRepository implements OrderRepository
{
    public function findByCriteria(Criteria $criteria): Collection
    {
        return $this->inner->findByCriteria(
            $criteria->withFilter('tenant_id', '=', $this->tenantId)
        );
    }
}

This is the pattern that prevents the classic "someone forgot the tenant clause" data leak — and it is cheap to add on day one.

In-memory parity for tests. The strongest day-to-day argument for the pattern: apply the same Criteria to a plain array collection in unit tests and to SQL in production. Your filtering logic gets tested in milliseconds without a database, provided your adapter supports both.

Composition over mega-criteria. Build small criteria in each use case and merge them (merge, and, or equivalent) rather than mutating one shared object. This keeps each piece independently assertable.

Request → Criteria mapping. In Laravel, add a thin CriteriaFromRequest that validates filter.* and sort against a whitelist and only then builds the Criteria. Keep this out of controllers and out of the Criteria itself.

Testing strategy. Assert equality of Criteria objects for use-case tests (requires the value objects to implement equals or __toString), and assert generated SQL/bindings for adapter tests. Two layers, both fast.

Gotchas and Tips

  • Treat it as vendored source. Five stars, zero dependents, and a release date that may be ahead of the current date — verify tags on Packagist before pinning ^x.y, and consider pinning an exact version. Wrap the library behind your own Criteria interface so replacing it later is a one-file change.
  • Whitelist filter fields and sort columns at the HTTP boundary. Criteria objects generally accept arbitrary field names; if you pass user input straight through, you have handed the client your schema and possibly an injection vector. Operator support is not identifier quoting — the adapter must map names to known columns.
  • Watch for discarded clones. Immutable criteria ($criteria->withFilter(...) returning a new instance) silently do nothing if you forget to assign. The symptom is "my filter isn't applied." Dump the criteria tree before it hits the adapter whenever a query looks wrong.
  • Don't leak persistence into the domain. If Criteria objects start carrying LIKE '%...%' or raw column names, you have re-created the coupling you adopted the pattern to remove.
  • Equality/caching consistency. If you cache results keyed by criteria, confirm the value objects serialize deterministically; mixing == and === across nested value objects is a common source of cache misses.
  • Keep eager-loading out of Criteria. Relationship loading is a persistence concern; let the repository decide based on the criteria it receives rather than accepting with() names from the caller.
  • Debugging loop: var_dump the criteria → log the final SQL with bindings → compare. If the SQL is right but results are wrong, the bug is in the adapter's operator mapping (> vs >=, case sensitivity, NULL handling), which is where nearly all adapter bugs live.
  • Extension points: if custom operators/filters are supported, implement them via the provided interface and register them in your adapter — otherwise fork only the adapter, never the package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle