Conditions

A lightweight, framework-agnostic Business Rule Evaluation Engine for PHP.

Obelaw Conditions

A lightweight, framework-agnostic Business Rule Evaluation Engine for PHP.

obelaw/conditions gives you a clean, elegant, and highly readable way to encapsulate complex business logic and evaluate rules dynamically. It’s perfectly suited for ERP systems, large applications, and for decoupling UI logic from core business rules.

Design Patterns Under the Hood

The package is inspired by enterprise software architecture and implements several proven design patterns:

  1. Specification Pattern — The heart of the package. Every business rule lives in its own dedicated class that extends the Condition base class and implements isSatisfiedBy(). This keeps each rule focused and honors the Single Responsibility Principle.
  2. Fluent Interface / Builder Pattern — The ConditionBus uses method chaining (is(), and(), or(), not()) to build complex logical queries that read like natural English.
  3. Composite Pattern (Evaluator) — The ConditionBus acts as a composite evaluator, combining multiple isolated Condition objects into a single executable logical tree.

Installation

Install the package via Composer:

composer require obelaw/conditions

Requirements:

  • PHP 8.1 or higher
  • Composer 2

Getting Started

1. Create a Condition

Every business rule is a dedicated class extending Obelaw\Conditions\Condition:

<?php

namespace App\Conditions;

use Obelaw\Conditions\Condition;

class IsPendingCondition extends Condition
{
    public function __construct(
        protected string $status = 'pending',
    ) {
    }

    public function isSatisfiedBy(mixed $candidate): bool
    {
        return $candidate->status === $this->status;
    }
}

A condition is a single, focused, testable unit of business logic.

2. Evaluate Rules with the ConditionBus

Collect conditions and evaluate them against a candidate in one fluent chain:

use Obelaw\Conditions\ConditionBus;
use App\Conditions\IsPendingCondition;
use App\Conditions\HasValidVatNumberCondition;
use App\Conditions\IsOverdueCondition;
use App\Conditions\IsVoidCondition;

$canCancel = ConditionBus::make()
    ->is(new IsPendingCondition())
    ->and(new HasValidVatNumberCondition())
    ->or(new IsOverdueCondition())
    ->not(new IsVoidCondition())
    ->evaluate($order); // bool

3. Compose Conditions Directly

Condition itself also exposes is(), and(), or(), and not() so you can build standalone, reusable rule trees that read like English:

use App\Conditions\IsPendingCondition;
use App\Conditions\IsApprovedCondition;
use App\Conditions\IsOverdueCondition;

$actionable = (new IsPendingCondition())
    ->and(new IsApprovedCondition())
    ->or(new IsOverdueCondition())
    ->not();

$actionable->isSatisfiedBy($order); // bool

Because composed conditions are still Condition objects, you can freely mix both styles:

$actionable = (new IsPendingCondition())
    ->or(new IsOverdueCondition());

ConditionBus::make()
    ->is($actionable)
    ->not(new IsVoidCondition())
    ->evaluate($order);

Operators Reference

ConditionBus

MethodBehaviour
make()Returns a new bus instance (an empty bus evaluates to true).
is(Condition $c)Adds a condition as the initial one, or as an AND.
and(Condition $c)Alias of is().
or(Condition $c)Adds a condition as an OR.
not(Condition $c)Adds a condition as an AND NOT.
evaluate(mixed $candidate)Runs all conditions and returns the final bool.

Condition Composition

MethodBehaviour
is(Condition $c)Alias of and().
and(Condition $c)Returns an AndCondition (both must pass).
or(Condition $c)Returns an OrCondition (either may pass).
not()Returns a NotCondition (negates the wrapped condition).

Evaluation runs left-to-right, and both the bus and composite conditions short-circuit — later conditions are skipped once the result is already decided.

Testing

composer test

The test suite (written with Pest) verifies the truth tables of is, and, or, and not, fluent chaining, short-circuiting, and composition:

Tests: 12 passed (32 assertions)

Contributing

Feel free to open issues or submit pull requests on the repository.

License

This package is open-sourced software licensed under the MIT license.