SkillAgentSearch skills...

TheChoice

Business Rule Engine on PHP

Install / Use

/learn @prohalexey/TheChoice
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

TheChoice - Business Rule Engine

GitHub license

A powerful and flexible Business Rule Engine for PHP that allows you to separate business logic from your application code.

Features

This library helps you simplify the implementation of complex business rules such as:

  • Complex discount calculations
  • Customer bonus systems
  • User permission resolution
  • Dynamic pricing strategies

Why use TheChoice? If you find yourself constantly modifying business conditions in your code, this library allows you to move those conditions to external configuration sources. You can even create a web interface to edit configurations dynamically.

Key Benefits

  • ✅ Rules written in JSON or YAML format
  • ✅ Store rules in files or databases
  • ✅ Serializable and cacheable configurations (PSR-16)
  • ✅ PSR-11 compatible container support
  • ✅ Extensible with custom operators and contexts
  • ✅ Rule Engine — evaluate multiple rules in a single run
  • ✅ Rule Registry — named rules with tags, version, and metadata
  • ✅ Rule Validator — static analysis of rules before execution
  • ✅ Evaluation Trace — step-by-step debugging of rule evaluation
  • ✅ Event System — PSR-14 lifecycle and node-level events for observability
  • ✅ Switch Node — multi-branch dispatch on a single context value
  • ✅ Fluent PHP Builder (DSL) — build rule trees programmatically without JSON/YAML
  • ✅ Node Exporter — serialize rule trees back to JSON or YAML
  • ✅ Storage variable references — use $storageKey as operator values

Table of Contents

Installation

composer require prohalexey/the-choice

Requirements: PHP 8.4+

Quick Start

<?php

use TheChoice\Builder\JsonBuilder;
use TheChoice\Container;
use TheChoice\Processor\RootProcessor;

// 1. Configure contexts — map names to classes that implement ContextInterface
$container = new Container([
    'visitCount'      => VisitCount::class,
    'hasVipStatus'    => HasVipStatus::class,
    'inGroup'         => InGroup::class,
    'withdrawalCount' => WithdrawalCount::class,
    'depositCount'    => DepositCount::class,
    'getDepositSum'   => GetDepositSum::class,
]);

// 2. Parse rules from a JSON file — returns a Root node (the rule tree)
$parser = $container->get(JsonBuilder::class);
$node = $parser->parseFile('rules/discount-rules.json');

// 3. Execute the rules
$rootProcessor = $container->get(RootProcessor::class);
$result = $rootProcessor->process($node);

Configuration Formats

JSON Configuration Example

{
  "node": "condition",
  "if": {
    "node": "collection",
    "type": "and",
    "nodes": [
      {
        "node": "context",
        "context": "withdrawalCount",
        "operator": "equal",
        "value": 0
      },
      {
        "node": "context",
        "context": "inGroup",
        "operator": "arrayContain",
        "value": [
          "testgroup",
          "testgroup2"
        ]
      }
    ]
  },
  "then": {
    "node": "context",
    "description": "Giving 10% of deposit sum as discount for the next order",
    "context": "getDepositSum",
    "modifiers": [
      "$context * 0.1"
    ],
    "params": {
      "discountType": "VIP client"
    }
  },
  "else": {
    "node": "value",
    "description": "Giving 5% discount for the next order",
    "value": "5"
  }
}

YAML Configuration Example

node: condition
if:
  node: collection
  type: and
  nodes:
  - node: context
    context: withdrawalCount
    operator: equal
    value: 0
  - node: context
    context: inGroup
    operator: arrayContain
    value:
      - testgroup
      - testgroup2
then:
  node: context
  context: getDepositSum
  description: "Giving 10% of deposit sum as discount for the next order"
  modifiers: 
    - "$context * 0.1"
  params:
    discountType: "VIP client"
else:
  node: value
  description: "Giving 5% discount for the next order"
  value: 5

Core Concepts

Node Types

Each node has a node property that describes its type and an optional description property for UI purposes.

Root Node

The root of the rules tree that maintains state and stores execution results. When the root node is omitted in the configuration, the library automatically wraps the top-level node in a root node (short syntax).

Properties:

  • storage - Named variables accessible in modifier expressions and as operator value references (e.g. $myVar). Values are resolved at parse time.
  • rules - Contains the first node to be processed

Example:

node: root
description: "Discount settings"
storage:
  $baseRate: 5
rules: 
  node: value
  value: 5

Value Node

Returns a static value.

Properties:

  • value - The value to return (can be array, string, or numeric)

Example:

node: value
description: "5% discount for next order"
value: 5

Context Node

Executes callable objects and can modify the global state which is stored in the "Root" node.

Properties:

  • break - Special property to stop execution early. When set to "immediately", the context result is saved to the Root node and evaluation stops — subsequent nodes in a collection are skipped. The final result is retrieved from the Root node.
  • context - Name of the context for calculations
  • modifiers - Array of mathematical modifiers
  • operator - Operator for calculations or comparisons
  • params - Parameters to set in context
  • priority - Priority for collection sorting
  • value - Value to compare against when using an operator. Can be a literal (0, "admin", [1,100]) or a $storageKey reference (resolved from Root storage at parse time).

Example:

node: context
context: getDepositSum
description: "Calculate 10% of deposit sum"
modifiers: 
  - "$context * 0.1"
params:
  discountType: "VIP client"
priority: 5

With Operator Example:

node: context
context: withdrawalCount
operator: equal
value: 0

With Break Example:

node: context
context: actionReturnInt
break: immediately

Condition Node

Conditional logic with if-then-else structure.

Properties:

  • if - Condition node (expects boolean result)
  • then - Node to execute if condition is true
  • else - Node to execute if condition is false (optional)

Example:

node: condition
if:
  node: context
  context: hasVipStatus
  operator: equal
  value: true
then:
  node: value
  value: 10
else:
  node: value
  value: 5

Collection Node

Contains multiple child nodes evaluated with a chosen logical strategy.

Properties:

  • type - Collection type: and, or, not, atLeast, or exactly
  • nodes - Array of child nodes
  • count - Required for atLeast and exactly types; specifies the threshold
  • priority - Priority used when this collection is nested inside another collection

Type reference:

| Type | Behaviour | |------|-----------| | and | Returns true if all children return true. Short-circuits on the first false. | | or | Returns true if at least one child returns true. Short-circuits on the first true. | | not | Returns true only if none of the children return true (NOR). Short-circuits on the first true. | | atLeast | Returns true if at least count children return true. Requires count. | | exactly | Returns true if exactly count children return true. Requires count. |

and Example:

node: collection
type: and
nodes:
  - node: context
    context: withdrawalCount
    operator: equal
    value: 0
  - node: context
    context: inGroup
    operator: arrayContain
    value:
      - testgroup
      - testgroup2

not Example — passes when the user is not blacklisted:

node: collection
type: not
nodes:
  - node: context
    context: isBlacklisted
    operator: equal
    value: true

atLeast Example — passes when at least 2 out of 3 conditions are met:

node: collection
type: atLeast
count: 2
nodes:
  - node: context
    context: withdrawalCount
    operator: equal
    value: 0
  - node: context
    context: visitCount
    operator: greaterThan
    value: 1
  - node: context
    context: hasVipStatus
    operator: equal
    value: true

exactly Example — passes when exactly 2 conditions are met:

node: collection
type: exactly
count: 2
nodes:
  - node: context
    context: withdrawalCount
    operator: equal
    value: 0
  - node: context
    context: visitCount
    operator: greaterThan
    value: 1
  - node: context
    context: hasVipStatus
    operator: equal
    value: true

Switch Node

Evaluates a single context value once and routes execution to the first matching case branch. Similar to a switch/case statement in P

View on GitHub
GitHub Stars25
CategoryDevelopment
Updated5d ago
Forks5

Languages

PHP

Security Score

95/100

Audited on Mar 31, 2026

No findings