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

Specification Laravel Package

rikbruil/specification

Implements the Specification pattern for Laravel, letting you encapsulate query constraints and business rules as reusable, composable classes. Combine specifications with and/or/not operators and apply them directly to Eloquent queries for cleaner, testable filtering logic.

Deep Wiki
Context7

Getting Started

The package implements the classical Specification pattern: you encapsulate a business rule in a class with a single isSatisfiedBy($candidate): bool method, then compose rules with and, or, and not. Start by pulling it in and opening the base interface and any composite classes first — those tell you the exact method names and generic types the package expects before you write your first rule.

composer require rikbruil/specification

Define a concrete rule by implementing the interface (or extending any provided abstract base):

use Rikbruil\Specification\Specification;

final class IsActive implements Specification
{
    public function isSatisfiedBy($user): bool
    {
        return $user->active === true;
    }
}

The first use case to try is filtering an in-memory collection before touching repos or queries — it lets you validate the rule and its composites in seconds:

$active = array_filter($users, fn ($u) => (new IsActive())->isSatisfiedBy($u));

From there, promote the rule into a repository or query builder where it actually earns its keep.

Implementation Patterns

Compose, don't inherit. The value of the pattern comes from combining small rules. Build a small library of atomic specs (IsActive, HasVerifiedEmail, CreatedSince) and compose them:

$spec = (new IsActive())
    ->and(new HasVerifiedEmail())
    ->or(new IsAdmin());

Check the package’s composite operators (typically and, or, not, and sometimes andNot/xor) and use the short-circuiting variants where the underlying candidate may be expensive to inspect.

Separate "collection" specs from "query" specs. A rule that runs in PHP (isSatisfiedBy) is specification-by-candidate. A rule that produces a query constraint (where, Doctrine Criteria, Eloquent Builder) is specification-by-query. Many packages expose both; if this one only ships the in-memory variant, keep that mental split and translate the same rule into scopes/Criteria on the persistence side so you don’t accidentally hydrate the entire table.

Repository integration. The cleanest day-to-day pattern is to hand a spec to a repository, not to leak isSatisfiedBy to callers:

final class UserRepository
{
    public function matching(Specification $spec): array
    {
        return array_values(array_filter(
            $this->all(),
            fn ($u) => $spec->isSatisfiedBy($u)
        ));
    }
}

Callers then say $users->matching($spec) and stay ignorant of the evaluation strategy.

Test specs in isolation. Because each spec is a pure predicate on a value object, unit tests are trivial and fast — assert isSatisfiedBy against 3–4 representative candidates (match, no-match, boundary, null). Only the composites need integration tests, and you can usually prove those with a truth-table.

Reuse specs as policy objects. The pattern is a natural fit for authorization (CanPublish, OwnsResource), eligibility (EligibleForDiscount), and DTO validation. Reusing the same spec class across these contexts is where the pattern pays off.

Gotchas and Tips

  • Keep specs stateless. A spec that stores intermediate state or memoizes per-candidate breaks composition and parallel evaluation. Constructor-injected configuration is fine; caching results is not.
  • Watch null and type coercion. isSatisfiedBy($candidate) will often be called with null when filtering optional relations. Add an explicit early return rather than relying on PHP’s loose comparisons.
  • Don’t over-compose. A five-deep chain with mixed and/or/not is unreadable and hard to debug. Group sub-specs into named classes (EligibleForCheckout) so the composite tree stays two levels deep.
  • Beware N+1 style pitfalls. isSatisfiedBy that touches a relation on every candidate silently becomes an N+1 query. If the spec needs related data, preload it, or push the rule down into a query spec instead.
  • Debug composite trees by rendering them. If composite classes implement __toString (or expose a describe()), you get a readable dump of the tree. If not, add a small helper that walks the composites — debugging and/or failures without this is painful.
  • Equality vs identity in composites. Some packages implement equals() on specs for query-deduping; if yours does, ensure your leaf specs implement it consistently or you’ll get duplicate predicates in generated queries.
  • Extension point: if you need a new operator (e.g. nand, xor, or a CountSpecification for collections), implement it as a new composite rather than adding branches inside existing ones — the whole point is that the composite vocabulary is open/closed.
  • Package maturity caveat. With a small star count, treat the API as low-churn-tolerant: pin the version in composer.json, wrap the package behind your own thin Specification interface, and keep the coupling to one folder. Swapping implementations later then costs a namespace change, not a refactor.
  • Licensing/MIT is friendly for internal and commercial use; no attribution burden beyond retaining the license file.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings