wallabag/rulerz-bundle
A Symfony bundle that integrates the RulerZ rule engine, letting you express business rules and filters in a readable DSL and apply them to Doctrine entities or collections. Useful for building dynamic specifications, query filters, and reusable rule-based logic in Symfony applications.
This is the Symfony integration layer for kphoen/rulerz (the wallabag fork). You almost never install this bundle for its own sake — you install it when you want the RulerZ rule engine auto-wired into a Symfony service container so you can filter Doctrine queries, arrays, or other "targets" with a string rule like points > 100 and group in :groups.
Install alongside RulerZ itself:
composer require wallabag/rulerz-bundle kphoen/rulerz
Register the bundle. On Symfony 4+/Flex add it to config/bundles.php:
RulerZ\RulerZBundle::class => ['all' => true],
On older Symfony add it to app/AppKernel.php.
Enable/configure it in config/packages/rulerz.yaml (defaults are usually fine):
rulerz:
cache: null # PSR-6/PSR-16 cache service or null
debug: '%kernel.debug%'
Look first for the rulerz service (type-hint RulerZ\RulerZ or the underlying executor). Do not instantiate RulerZ manually — the bundle gives you a configured instance with the compilers/cache it supports.
First use case — filter a Doctrine QueryBuilder from a repository with a user-supplied rule string:
use RulerZ\RulerZ;
public function activeAdmins(RulerZ $rulerz, string $rule): array
{
$qb = $this->createQueryBuilder('u');
return $rulerz->filterSpec($qb, $rule)... // or $rulerz->filter($qb, $rule)
}
filter()/matching() on a QueryBuilder returns the builder with the rule applied (not executed), so you keep control over pagination and hydration. On raw arrays, filter() returns the filtered array directly.
Rules as a feature, not a gimmick. The highest-value day-to-day pattern is letting users (or config files) express filters as rules instead of writing a switch over filter columns. Keep the rule string in a form field or database column, pass it to rulerz, and bind parameters separately:
$rulerz->filter($target, 'status = :status and score >= :min', [
'status' => 'active',
'min' => 50,
]);
Target the right abstraction. RulerZ dispatches on the target type, so the same rule string can run against:
QueryBuilder (translator returns SQL, stays in the DB — the pattern you want for lists/pagination),array (in-memory filtering; fine for small fixtures, tests, or cached result sets),Design your service around "a target plus a rule," and keep the target choice in the caller so the same rule can be reused across storage layers.
Specifications for reusable business rules. Encapsulate non-trivial rules as spec classes and inject them, rather than scattering rule strings:
final class IsActiveSpec implements \RulerZ\Spec\Specification
{
public function getRule(): string
{
return 'status = :status';
}
public function getParameters(): array
{
return ['status' => 'active'];
}
}
Call $rulerz->satisfiesSpec($entity, $spec) for single-entity checks and filterSpec()/matchingSpec() for collections. This keeps named business rules testable and greppable, and avoids user-entered strings where none are needed.
Compose with the container. Type-hint RulerZ\RulerZ in services and controllers; the bundle's service is shared and already has compilers registered. Tag spec services (e.g. rulerz.specification) where supported so they can be collected and exposed by name in an admin UI, a "saved filters" feature, or a config-driven rule set.
Combine with the form/query layer. Typical workflow: a filter DTO → a rule string + parameters → matching() on a QueryBuilder → paginate. This lets you expose a "rule editor" (advanced search) without writing a query builder per field, and it keeps the rule portable to an array target for unit tests.
Cache parsed rules. Enable a cache adapter in dev/prod config when rules come from user input; RulerZ parses the rule string on every call, and a PSR cache removes repeated parsing in request-heavy paths (e.g. per-row satisfies() checks in a Twig loop — better still, pre-filter the collection).
wallabag fork with effectively no downstream dependents and near-zero stars. Pin the version in composer.json, and check that its kphoen/rulerz constraint and Symfony version constraints match your stack before adopting. If you are not already inside the wallabag ecosystem, weigh kphoen/rulerz-bundle (or wiring RulerZ manually) first.filter() vs matching() semantics differ by target. On a QueryBuilder you get the builder back to further modify; on arrays you get results. Do not assume chaining works identically across targets — assert the return type in tests.in-style lists can leak schema details or produce expensive queries. Validate allowed identifiers/operators before handing a string to the engine, and always bind values as parameters.satisfies() on a managed Doctrine entity works in memory; on a QueryBuilder it compiles to SQL. Mixing the two inside one code path (e.g., checking a proxied entity before flush) is a common source of confusing results.cache option expects a service reference/ID, not an inline adapter class. Passing true or a raw DSN will fail during container compilation; leave it null in tests to avoid stale parsed-rule state.debug flag in dev to get clearer parser errors, and keep rule strings in one place (specs or a config file) so a failing rule is easy to isolate. When a rule "silently matches nothing," check the parameter types first — string "1" vs int 1 in Doctrine conditions bites often.How can I help you explore Laravel packages today?