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

Rulerz Bridge Laravel Package

kphoen/rulerz-bridge

Bridge package that brings the RulerZ rule engine to Laravel, letting you filter Eloquent models and collections with reusable rule strings. Define specifications once and apply them consistently across database queries and in-memory data.

View on GitHub
Deep Wiki
Context7

Getting Started

First, check what you actually installed. kphoen/rulerz-bridge is a Symfony bundle (RulerzBundle with a DependencyInjection\Extension and compiler passes). Despite the description, it ships no Laravel service provider, no facade, and no auto-discovery. Day one of using it in a Laravel app is therefore a decision point, not an install:

composer require kphoen/rulerz-bridge -W
cat vendor/kphoen/rulerz-bridge/composer.json   # look at require: symfony/framework-bundle?
composer why-not kphoen/rulerz-bridge           # expect illuminate/* conflicts

If that require block references symfony/framework-bundle or symfony/dependency-injection, remove the bridge and keep only the core:

composer remove kphoen/rulerz-bridge
composer require kphoen/rulerz

Where to look first — in the core, in this order: RulerZ\RulerZ (the three public methods: satisfies, filter, apply), RulerZ\Spec\Specification, then src/Target/* (the compilation targets). Those four files are the entire mental model. Ignore the bridge source except as a reference for how someone else registered operators.

Minimal wiring (~50 lines you own). Register the engine as a singleton, mirroring what the Symfony bundle's DI extension does:

// app/Providers/RulerzServiceProvider.php
$this->app->singleton(RulerZ::class, function () {
    // Constructor args vary across core versions — read vendor/kphoen/rulerz/src/RulerZ.php
    $rulerz = new RulerZ(
        new Compiler(new EvalEvaluator()),
        [new Target\ArrayTarget(), new Target\DoctrineORMTarget()],
        [new Operator\Like(), new Operator\In()]
    );

    return $rulerz;
});

Add a facade only if your team likes facades:

class Rulerz extends Facade
{
    protected static function getFacadeAccessor(): string { return RulerZ::class; }
}

First use case — kill an if chain. Take the gnarliest eligibility rule in the codebase (discount, gating, tier) and express it as a string:

Rulerz::satisfies(
    'user.group in :groups and user.age >= :min_age',
    $user,
    ['groups' => ['admin', 'staff'], 'min_age' => 18]
);

Property paths (user.group) resolve through Symfony PropertyAccess, so they map to getters or public properties — not Eloquent attributes through __get in every case. Verify against a real model before writing a second rule.

Implementation Patterns

Rules as strings in config, specs as classes. The two workable shapes are:

// config/rules.php — editable without touching a class
'promotions' => [
    'free_shipping' => 'cart.total >= 50 and cart.country in :eligible',
],
// app/Rules/DiscountEligible.php — composable, testable
class DiscountEligible implements Specification
{
    public function getRule(): string { return 'user.tier = "gold" and order.total > 100'; }
    public function getParameters(): array { return []; }
}

Use config strings for rules ops tweaks weekly; use Specification classes when rules need to be composed (->and()), injected, or unit-tested independently. Avoid a hybrid where half the truth lives in YAML and half in PHP.

Eloquent is the weak spot — choose a path deliberately. RulerZ has no native Eloquent target. Two options, both real:

  1. Filter in PHP (start here). Hydrate, then filter:

    $collection = Product::where('active', true)->get();
    $cheap = Rulerz::filter($spec, $collection);         // returns a filtered collection/iterator
    $ok    = Rulerz::satisfies($spec, $product);          // single-object check
    

    Fine for hundreds to low thousands of rows. Not fine for a catalogue page.

  2. Write a custom compilation target (when the PHP path hurts). Implement RulerZ\Compiler\CompilationTarget producing an Eloquent Builder, so the rule becomes WHERE clauses. This is the single most valuable extension point in the package and the one you will actually maintain. Budget a sprint; make it the only place SQL semantics leak in.

Pass parameters, never interpolate. Build rule strings from code, not from user input; put every variable in the parameter array. There is no escaping story in the DSL and none is needed if you follow this.

Compose specs instead of concatenating strings. String concatenation across modules produces unreadable rules and duplicate operators; Specification::and()/or() keeps provenance.

Compile once, cache aggressively. The compiler turns a rule string into PHP/SQL. Use the file/eval evaluator with a persistent cache directory keyed on the rule hash — or warm rules at deploy. Without caching, latency scales with rule count and every request pays for parsing.

Test with fixtures, not vibes. For each production rule, store (rule, parameters, input, expected) tuples and assert them. This is your only defence against a silent PHP-upgrade behaviour change, and it doubles as documentation.

Integrate at boundaries. Wrap RulerZ calls in a small RuleEngine service (isEligible(User, Specification), filterQuery(...)) so application code never imports RulerZ\*. If you later replace the DSL, you change one class.

Gotchas and Tips

  • The packaging mismatch is the first trap. No Laravel provider, no facade, no config/rulerz.php published. Anything in the README implying framework plumbing is Symfony plumbing. Reimplementing the bridge in a service provider is not porting a dependency — it is owning ~50 lines you should own anyway.
  • Dormant upstream. Expect PHP 7.x-era composer.json constraints to collide with Laravel 10/11 (PHP 8.1+). Run composer why-not before you build a habit on it; a lockfile conflict can freeze your framework upgrade path. Pin the version, and decide up front whether you forkmaintain the core.
  • satisfies() vs filter() are not interchangeable. filter() returns an iterator/collection and may short-circuit differently; satisfies() is a boolean check on one subject. Don't assert one against the other in tests.
  • Operator names are global. Registering a custom operator named in or like shadows built-ins or throws; namespace your custom ones (app_has_feature) and register them in one place.
  • DSL string quoting is the #1 authoring error. user.tier = "gold" vs user.tier = gold behave differently — the latter is parsed as a field path. Prefer parameters over literals so the quoting question disappears.
  • Silent failures are the expensive ones. Unknown operators throw, but a valid rule against a field that resolves to null usually evaluates false rather than erroring. A rule that quietly stops matching for everyone after a refactor looks identical to "nobody qualified this week." Log rule + params + result at the call site for a release cycle.
  • N+1 hides inside filter(). Filtering a hydrated Eloquent collection means every relationship the rule touches lazy-loads. Eager-load everything the rule references, or push the rule into a query target.
  • Property access surprises. user.group goes through PropertyAccess: getters, is*/has*, public properties, array keys. Eloquent accessors work because they're getters; __get-only dynamic attributes may not. Test with a real hydrated model, not a stdClass.
  • Debug by dumping the compiled rule. Before the executor runs, log the compiled output — most "wrong result" bugs are visible there in one line, and you'll never find them by staring at the DSL.
  • Extension points worth knowing: custom Operator, custom CompilationTarget, and the executor/evaluator choice (eval vs file cache). Everything else is fixed; don't fight it.
  • Performance ceiling is CPU-bound and in-process. No service tier, no query pushdown by default. Hundreds of thousands of records is database work, not rule-engine work — this is a business-logic tool, not a filtering engine.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core