A powerful and flexible Business Rule Engine for PHP that allows you to separate business logic from your application code.
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.
- ✅ Rules written in JSON or YAML format
- ✅ Store rules in files or databases
- ✅ Serializable and cacheable configurations
- ✅ PSR-11 compatible container support
- ✅ Extensible with custom operators and contexts
composer require prohalexey/the-choice
{
"node": "condition",
"if": {
"node": "collection",
"type": "and",
"elements": [
{
"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"
}
}
node: condition
if:
node: collection
type: and
elements:
- 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
<?php
use TheChoice\Container;
// Configure contexts in the PSR-11 compatible container
$container = new Container([
'visitCount' => VisitCount::class,
'hasVipStatus' => HasVipStatus::class,
'inGroup' => InGroup::class,
'withdrawalCount' => WithdrawalCount::class,
'depositCount' => DepositCount::class,
'utmSource' => UtmSource::class,
'contextWithParams' => ContextWithParams::class,
'action1' => Action1::class,
'action2' => Action2::class,
'actionReturnInt' => ActionReturnInt::class,
'actionWithParams' => ActionWithParams::class,
]);
// Create a parser
$parser = $container->get(JsonBuilder::class);
// Load rules from file or other sources
$rules = $parser->parseFile('rules/discount-rules.json');
// Get the processor
$resolver = $container->get(ProcessorResolverInterface::class);
$processor = $resolver->resolve($rules);
// Execute the rules
$result = $processor->process($rules);
Each node has a node
property that describes its type and an optional description
property for UI purposes.
The root of the rules tree that maintains state and stores execution results.
Properties:
storage
- Container for variablesrules
- Contains the first node to be processed
Example:
node: root
description: "Discount settings"
rules:
node: value
value: 5
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
Executes callable objects and can modify the global state which is stored in the "Root" node.
Properties:
break
- Special property to stop execution ("immediately"
stops and returns context result)context
- Name of the context for calculationsmodifiers
- Array of mathematical modifiersoperator
- Operator for calculations or comparisonsparams
- Parameters to set in contextpriority
- Priority for collection sortingvalue
- Default value for the$context
variable
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
Contains multiple nodes with AND/OR logic.
Properties:
type
- Collection type (and
oror
)elements
- Array of child nodespriority
- Priority for nested collections
Example:
node: collection
type: and
elements:
- node: context
context: withdrawalCount
operator: equal
value: 0
- node: context
context: inGroup
operator: arrayContain
value:
- testgroup
- testgroup2
Conditional logic with if-then-else structure.
Properties:
if
- Condition node (expects boolean result)then
- Node to execute if condition is trueelse
- Node to execute if condition is false
The following operators are available for context nodes:
ArrayContain
- Check if array contains valueArrayNotContain
- Check if array doesn't contain valueEqual
- Equality comparisonGreaterThan
- Greater than comparisonGreaterThanOrEqual
- Greater than or equal comparisonLowerThan
- Less than comparisonLowerThanOrEqual
- Less than or equal comparisonNotEqual
- Not equal comparisonNumericInRange
- Check if number is within rangeStringContain
- Check if string contains substringStringNotContain
- Check if string doesn't contain substring
Modifiers allow you to transform context values using mathematical expressions. Use the predefined $context
variable in your expressions.
For more information about calculations, see: https://github.com/chriskonnertz/string-calc
Create custom context classes by implementing the required interface and register them in the container.
Extend the system by creating custom operators and adding them to the container.
Configurations can be serialized and cached for improved performance.
For more detailed examples and usage patterns, see the test files in the test/
directory, especially the container configuration examples.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.