PHP-ETL - Rule Engine
Rule Engine - Transform data with typed rules
Rule Engine
The Rule Engine (Oliverde8\Component\RuleEngine) is a standalone component that computes a value by
applying a rule against an associative array. It’s not tied to PHP-ETL’s chain paradigm — it can be used on its
own — but within PHP-ETL it powers several operations:
- Rule Transformer — build each output column from a rule
- Filter Data — keep or discard an item based on a rule’s result
- If — route an item to a branch based on a rule’s result
- Switch — route an item to the first matching case’s branch
From a Tree to a Flat Result
Source data is often a tree — nested arrays, several levels deep, as you’d get parsing JSON or a complex API response. Rules read that tree and combine it into whatever shape you need, typically a flat associative array.
GetRuleConfig walks down one level per array entry, so ['customer', 'address', 'city'] reads three levels
deep:
$data = [
'customer' => [
'name' => ['first' => 'Jane', 'last' => 'Doe'],
'address' => ['city' => 'Paris'],
],
];
new GetRuleConfig(['customer', 'name', 'first']); // 'Jane'
new GetRuleConfig(['customer', 'address', 'city']); // 'Paris'
Rules compose: a rule’s parameters can themselves be rules, so you can combine several tree branches into one
value. RuleTransformConfig::addColumn() calls this once per output column, so however deep each read went, the
result is always a flat associative array — one key per column:
use Oliverde8\Component\PhpEtl\OperationConfig\Transformer\RuleTransformConfig;
use Oliverde8\Component\RuleEngine\RuleConfig\GetRuleConfig;
use Oliverde8\Component\RuleEngine\RuleConfig\ImplodeRuleConfig;
$config = (new RuleTransformConfig(add: false))
->addColumn('FullName', new ImplodeRuleConfig(
values: [
new GetRuleConfig(['customer', 'name', 'first']),
new GetRuleConfig(['customer', 'name', 'last']),
],
with: ' ',
))
->addColumn('City', new GetRuleConfig(['customer', 'address', 'city']));
// Result: ['FullName' => 'Jane Doe', 'City' => 'Paris']
Building a Nested Result
addColumn() can also do the opposite: build a nested result instead of a flat one. Put a / in the column
name and each segment becomes one level of the output array — independently of how the value was read:
$data = ['first_name' => 'Jane', 'last_name' => 'Doe', 'town' => 'Paris'];
$config = (new RuleTransformConfig(add: false))
->addColumn('customer/name/first', new GetRuleConfig('first_name'))
->addColumn('customer/name/last', new GetRuleConfig('last_name'))
->addColumn('customer/city', new GetRuleConfig('town'));
// Result: ['customer' => ['name' => ['first' => 'Jane', 'last' => 'Doe'], 'city' => 'Paris']]
Dynamic Columns from Context
A column name can also contain a {...} placeholder, resolved against the context parameters passed to
$chainProcessor->process($items, $parameters). When the resolved value is an array, one column is generated
per value instead of a single one — useful when the number of columns depends on something you only know at
runtime, like a list of locales:
$config = (new RuleTransformConfig(add: false))
->addColumn('name-{@context/locales}', new GetRuleConfig(['name', '@context/locales']));
$chainProcessor->process($items, ['locales' => ['fr_FR', 'en_US']]);
// Given ['name' => ['fr_FR' => 'Mon Produit', 'en_US' => 'My Product']]:
// Result: ['name-fr_FR' => 'Mon Produit', 'name-en_US' => 'My Product']
@context/locales in field isn’t a literal key — for each generated column it resolves to the locale
currently being expanded, so the rule reads the matching translation each time. Use get for this rather than
ExpressionRuleConfig: get returns an empty value for a missing locale, ExpressionRuleConfig would throw.
You can combine several placeholders ('{@context/scope}-{@context/locale}' generates one column per
combination) and mix this with a nested column name ('translations/{@context/locales}'). See
Flattening Complex Data for a full worked example, reading a JSON
file and writing the result to CSV.
Typed Rules
Each rule has a matching *RuleConfig class under Oliverde8\Component\RuleEngine\RuleConfig, giving full IDE
autocomplete and constructor validation instead of guessing array shapes. Wherever an operation accepts a rule
(RuleTransformConfig::addColumn(), FilterDataConfig/IfConfig/SwitchConfig’s rules), it accepts a typed
RuleConfigInterface — they can even be mixed with the legacy array syntax column-by-column in the same
RuleTransformConfig, since each column’s rule is resolved independently.
Available Typed Rules
| Class | Description |
|---|---|
GetRuleConfig(string\|array $field) |
Fetches a value from the input array by key, or walks a nested path when $field is an array. |
ConstantRuleConfig(mixed $value) |
Returns a constant, static value. |
ImplodeRuleConfig(array $values, string $with = '') |
Concatenates the results of $values (each a RuleConfigInterface) using $with as delimiter. |
StrToLowerRuleConfig(RuleConfigInterface $value) |
Converts the result of $value to lowercase. |
StrToUpperRuleConfig(RuleConfigInterface $value) |
Converts the result of $value to uppercase. |
ExpressionRuleConfig(string $expression, array $values = []) |
Leverages Symfony Expression Language. $values is a map of variable name to a RuleConfigInterface resolved before evaluation. |
Need custom logic beyond these six? See Custom Rules.
A legacy array-based syntax also still works everywhere a rule is accepted, but it’s deprecated — see Migrating to Typed Rules to convert existing chains, or Legacy Array Syntax for reference.