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

Rule Builder Laravel Package

apility/rule-builder

apility/rule-builder is a Laravel package for defining and evaluating dynamic rules through a fluent PHP API, making it easier to build flexible conditions, filters, and business logic without hardcoding each case.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: apility/rule-builder is a framework-agnostic PHP library with 1 GitHub star, 0 dependents, and no Laravel integration. There is no service provider, no facade, no publishable config. You are adding a low-adoption dependency, not an ecosystem package — so the goal of day one is to read the source before you commit to it.

composer require apility/rule-builder

Read the repository in this order:

  1. README.md — the fluent builder surface and the evaluation entry point.
  2. composer.json — PHP constraint and whether it pulls in any illuminate/* packages (conflict risk with your Laravel version).
  3. src/ — the public interfaces. Note which classes are final, which are interfaces you can implement, and where exceptions originate.
  4. tests/ — with a package this small, tests are the real documentation. Copy their setup/assert pattern into your own test suite rather than trusting the README alone.

Wrap it immediately. Create app/Support/Rules/ and expose one narrow class, e.g. OrderEligibilityRule, that takes a plain array or DTO in and returns a boolean (plus a reason, if the library gives you one). Nothing else in your codebase should import the vendor namespace. Your first real use case should be small and low-risk: free-shipping eligibility, discount qualification, or notification routing — a predicate over an already-loaded Eloquent model converted to an array.

final class FreeShippingRule
{
    public function __construct(private array $definition) {}

    public function qualifies(Order $order): bool
    {
        return (new RuleEvaluator())->evaluate($this->definition, [
            'total'      => (float) $order->total,
            'country'    => $order->shipping_country,
            'item_count' => $order->items()->count(),
        ]);
    }
}

Treat the new RuleEvaluator() line as a placeholder — substitute the actual entry point from src/ before shipping.

Implementation Patterns

Bind factories, not instances. Fluent builders are almost always mutable. Do not register a builder as a singleton in a service provider; you will leak state between evaluations in a queue worker. If you want container access, bind a factory or a callable that returns a fresh builder, and resolve that.

Persist the definition, never the object. Store the rule as JSON in a json column and hydrate through your wrapper on read. serialize() of a builder object will silently break across minor versions of an unmaintained dependency; a JSON blob survives a fork or a replacement library.

protected $casts = ['rule_definition' => 'array'];

Validate at the boundary. Any rule payload that can come from a user (an admin rule editor, an import) needs a FormRequest or custom ValidationRule that whitelists field names and operators before the library sees it. Rule engines are a classic injection surface: an unwhitelisted field name is effectively an arbitrary attribute read.

Integrate through Laravel's grain, not around it. Use it inside a policy for authorization decisions, a Rule validation object, a queued job that decides which notifications to dispatch, or a pricing/checkout service. Keep the evaluation synchronous and side-effect free; if the rule needs data from the database, resolve that data before evaluating and pass a flat array. Otherwise you get N+1 queries hidden inside what looks like a pure function.

Test with a data provider. The pattern that pays off fastest:

#[DataProvider('ruleCases')]
public function test_free_shipping(array $definition, array $order, bool $expected): void

This gives you a regression suite you own, which matters enormously when the package isn't covered by community testing.

Decorator for observability. Wrap the evaluator in a logger that records (definition, input, outcome, duration). When a customer says "the discount didn't apply," this is the only thing that lets you answer without a debugger.

Gotchas and Tips

Adoption risk is the headline gotcha. One star and zero dependents means no community bug reports, no Stack Overflow answers, and no guarantee of maintenance. Pin the exact version ("apility/rule-builder": "1.2.3", not ^1.2), check the code into your mental "vendored" category, and be ready to fork. If the license is MIT (verify in composer.json), forking into your own namespace is cheap and is the responsible move for anything business-critical.

Comparison semantics are where these libraries break. Check whether the evaluator uses == or ===, how it coerces "5" versus 5, and what it does with null. Write tests for 0, "0", "", null, and false explicitly — these are the values that make rule engines quietly wrong.

Empty rule sets. Decide deliberately whether a rule with no conditions matches everything or nothing. Most implementations pick vacuous truth; make that explicit in your wrapper and test it, because "no conditions = applies to everyone" is a costly default in pricing.

Don't let vendor exceptions reach the HTTP layer. Catch them in your wrapper and convert to a domain exception, so a malformed stored rule surfaces as a 500 you control rather than a library-specific class name in your logs.

Extension points, if any. Check for interfaces for custom operators, validators, or resolvers before you subclass. If the builder is final or closed, composition is the only path — wrap rather than reach into internals with reflection.

Debugging recipe. When an evaluation surprises you, dump the normalized rule structure (after the builder flattens it) alongside the input array. Most "wrong result" reports turn out to be either an input-array key mismatch or a string/number comparison, and seeing the normalized form side by side with the input resolves it in seconds.

Have an exit plan. Because your wrapper is the only thing touching the vendor code, swapping in a maintained alternative or 200 lines of your own evaluator is an afternoon's work. Keep it that way: no vendor types in constructors, no vendor constants in Blade, no rule JSON saved in a shape only this library can read.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
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