PHP code example of leeovery / rules-engine

1. Go to this page and download the library: Download leeovery/rules-engine library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

leeovery / rules-engine example snippets


use LeeOvery\RulesEngine\Condition;
use LeeOvery\RulesEngine\Facades\RulesEngine;

// Publish version 1 of a rule set.
RulesEngine::ruleSet('merchant-categories')
    ->rule('tesco', Condition::whereStartsWith('merchant', 'TESCO'), 'groceries')
    ->rule('shell', Condition::whereStartsWith('merchant', 'SHELL'), 'fuel')
    ->rule('fallback', Condition::always(), 'uncategorised')
    ->publish();

// Resolve it against facts.
RulesEngine::for('merchant-categories')
    ->with(['merchant' => 'TESCO STORES 3021'])
    ->firstValue(); // 'groceries'

// Add a rule. This publishes version 2, and version 1 stays as it was.
RulesEngine::ruleSet('merchant-categories')
    ->addRule('pret', Condition::whereContains('merchant', 'PRET'), 'eating-out', before: 'fallback')
    ->publish();

enum RuleSet: string
{
    case MerchantCategories = 'merchant-categories';
}

enum Category: string
{
    case Groceries = 'groceries';
    case Fuel = 'fuel';
    case EatingOut = 'eating-out';
    case Uncategorised = 'uncategorised';
}

use LeeOvery\RulesEngine\Condition;
use LeeOvery\RulesEngine\Facades\RulesEngine;

RulesEngine::ruleSet(RuleSet::MerchantCategories)
    ->description('Spending categories by merchant')
    ->rule('tesco', Condition::whereStartsWith('merchant', 'TESCO'), Category::Groceries)
    ->rule('shell', Condition::whereStartsWith('merchant', 'SHELL'), Category::Fuel)
    ->rule('pret', Condition::whereContains('merchant', 'PRET'), Category::EatingOut)
    ->rule('fallback', Condition::always(), Category::Uncategorised)
    ->publishedBy('alex')
    ->note('Starting categories')
    ->publish();

$match = RulesEngine::for(RuleSet::MerchantCategories)
    ->with(['merchant' => 'TESCO STORES 3021', 'amount' => 4250])
    ->first();

$match->as(Category::class); // Category::Groceries
$match->key;                 // 'tesco'
$match->version;             // 1

$match->ruleSet;    // 'merchant-categories'
$match->version;    // 1
$match->versionId;  // the rule_set_versions id
$match->ruleId;     // the rules id
$match->uuid;       // the rule's permanent uuid
$match->key;        // 'tesco'
$match->position;   // 1
$match->value;      // 'groceries', decoded from JSON
$match->score;      // 1

$match->metadata();                    // all metadata, or null
$match->metadata('source.answer');     // a key, with dot notation
$match->metadata('owner', 'unknown');  // with a default
$match->as(Category::class);           // the enum case for the value

RulesEngine::for(RuleSet::MerchantCategories)
    ->with('merchant', 'SHELL 0141')
    ->with(['amount' => 6500])
    ->firstValue(); // 'fuel'

RulesEngine::ruleSet(RuleSet::MerchantCategories)
    ->updateRule('pret', value: Category::Groceries)
    ->publishedBy('sam')
    ->note('Pret is lunch shopping')
    ->publish();

RulesEngine::ruleSet(RuleSet::MerchantCategories)
    ->addRule('aldi', Condition::whereStartsWith('merchant', 'ALDI'), Category::Groceries, after: 'tesco')
    ->moveRule('pret', before: 'tesco')
    ->renameRule('shell', 'shell-garages')
    ->removeRule('fallback')
    ->publish();

$preview = RulesEngine::ruleSet(RuleSet::MerchantCategories)
    ->addRule('costa', Condition::whereContains('merchant', 'COSTA'), Category::EatingOut)
    ->preview();

$preview->version;          // the number it would get
$preview->previousVersion;  // the version it's compared with
$preview->rules;            // the complete rule list, as RuleDefinition values with keys and uuids
$preview->changes;          // the RuleSetChanges it would make

RulesEngine::ruleSet(RuleSet::MerchantCategories)
    ->publishedBy('alex')
    ->note('Back to the starting categories')
    ->revertTo(1);

$changes = RulesEngine::changes(RuleSet::MerchantCategories, from: 1, to: 3);

$changes->added;             // RuleReference values, each with a uuid and key
$changes->removed;
$changes->renamed;           // RenamedRule values, each with a uuid, from and to
$changes->conditionChanged;
$changes->valueChanged;
$changes->metadataChanged;
$changes->moved;
$changes->isEmpty();

RulesEngine::for(RuleSet::MerchantCategories)
    ->asOf($transaction->booked_on)         // the version in force on that date
    ->knownAt($transaction->categorised_at) // only versions published by then
    ->with(['merchant' => $transaction->merchant])
    ->first();

RulesEngine::ruleSet(RuleSet::UkDividendRates)
    ->effectiveFrom('2026-04-06')
    ->rule('rates', Condition::always(), [
        'allowance' => 50000,
        'ordinary' => '0.1075',
        'upper' => '0.3575',
        'additional' => '0.3935',
    ])
    ->publish();

$rates = RulesEngine::for(RuleSet::UkDividendRates)->asOf($dividend->paid_on)->firstValue();

use Illuminate\Support\Facades\Event;
use LeeOvery\RulesEngine\Events\RuleSetVersionPublished;

Event::listen(function (RuleSetVersionPublished $event): void {
    foreach ($event->changes->valueChanged as $rule) {
        // re-categorise the transactions that $rule->uuid answered
    }
});

use LeeOvery\RulesEngine\Operator;

// Equals (default operator)
Condition::where('merchant', 'TESCO')

// With an explicit operator, as a string or an Operator case
Condition::where('amount', '>', 10000)
Condition::where('amount', Operator::GreaterThan, 10000)

// AND and OR
Condition::whereStartsWith('merchant', 'SHELL')->where('amount', '<', 2000)
Condition::whereContains('merchant', 'PRET')->orWhereContains('merchant', 'COSTA')

// Nested: type = 'card' AND (country = 'UK' OR country = 'IE')
Condition::where('type', 'card')
    ->where(fn ($q) => $q->where('country', 'UK')->orWhere('country', 'IE'))

Condition::whereIn('status', ['pending', 'booked'])
Condition::whereNotIn('type', ['refund', 'fee'])
Condition::whereNot('archived', true)
Condition::whereNull('merchant')
Condition::whereNotNull('merchant')
Condition::whereContains('merchant', 'PRET')
Condition::whereStartsWith('merchant', 'TESCO')
Condition::whereEndsWith('merchant', 'LTD')

// Every method has an 'or' variant
Condition::where('a', 1)->orWhereIn('b', [2, 3])

Condition::always() // matches any facts
Condition::else()   // the same, reads well as a last rule

RulesEngine::for(RuleSet::MerchantCategories)->with($facts)->cache(3600)->first();   // seconds
RulesEngine::for(RuleSet::MerchantCategories)->with($facts)->withoutCache()->first();

// config/rules-engine.php
return [
    'cache' => [
        'ttl' => 3600, // seconds; null turns caching off by default
        'prefix' => 'rules-engine',
    ],
];

RulesEngine::versions(RuleSet::MerchantCategories);                  // every RuleSetVersion, oldest first
RulesEngine::versions(RuleSet::MerchantCategories)->load('rules');
RulesEngine::version(RuleSet::MerchantCategories, 2);                // one version; its rules load on access
RulesEngine::newestVersion(RuleSet::MerchantCategories);             // the version published last, whatever its effective date
RulesEngine::changes(RuleSet::MerchantCategories, from: 1, to: 2);   // what changed between them
bash
php artisan migrate
bash
php artisan vendor:publish --tag="rules-engine-config"