PHPackages                             goplasmatic/datalogic - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. goplasmatic/datalogic

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

goplasmatic/datalogic
=====================

PHP bindings for datalogic-rs — a JSONLogic (json-logic) rules engine with a native Rust core via PHP FFI. Identical semantics across PHP, Node.js, WASM, Python, Go, Java, .NET, and Rust.

v5.1.1(3w ago)01Apache-2.0PHPPHP ^8.4

Since May 14Pushed 1mo agoCompare

[ Source](https://github.com/GoPlasmatic/datalogic-php)[ Packagist](https://packagist.org/packages/goplasmatic/datalogic)[ Docs](https://github.com/GoPlasmatic/datalogic-rs)[ RSS](/packages/goplasmatic-datalogic/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (3)Versions (5)Used By (0)

goplasmatic/datalogic
=====================

[](#goplasmaticdatalogic)

[![Packagist](https://camo.githubusercontent.com/15e5ac3ec9865fb243dd8a95547f0b7befaf9c2a124034835c9dfff60c7cb360/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f676f706c61736d617469632f646174616c6f676963)](https://packagist.org/packages/goplasmatic/datalogic)[![CI](https://github.com/GoPlasmatic/datalogic-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/GoPlasmatic/datalogic-rs/actions/workflows/ci.yml)[![License: Apache 2.0](https://camo.githubusercontent.com/a549a7a30bacba7bfceebdc207a8e86c3f2c02995a2527640dca30048fd2b64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667)](https://opensource.org/licenses/Apache-2.0)

Part of [datalogic-rs](https://github.com/GoPlasmatic/datalogic-rs) — one engine, every runtime.

PHP bindings for [datalogic-rs](https://github.com/GoPlasmatic/datalogic-rs), the JSONLogic rules engine with one Rust core and official bindings for Rust, Node.js, the browser (WASM), Python, Go, Java, .NET, and PHP. Same rules, same semantics: every binding runs the same core and passes the same 1,532-case conformance battery (53 suites). Compile once, evaluate many, natively in PHP.

For the cross-runtime overview and the API-tier model every binding implements, see the [repo README](https://github.com/GoPlasmatic/datalogic-rs#readme).

> **New in v5.** This package is new: there is no v4 PHP artifact. If you are coming from the v4 Rust crate or the v4 `@goplasmatic/datalogic` WASM package, the engine's v4 → v5 changes are catalogued in [MIGRATION.md](https://github.com/GoPlasmatic/datalogic-rs/blob/main/MIGRATION.md).

Install
-------

[](#install)

```
composer require goplasmatic/datalogic
```

Requires PHP 8.4+ with `ext-ffi` enabled. The binding is a PHP FFI wrapper over the engine's C ABI; the Composer package ships the native library under `lib/-/` for every supported platform, and the FFI loader picks the right one at runtime. No Rust toolchain needed.

PlatformArchitecturesLinuxx86\_64, aarch64macOSx86\_64, arm64Windowsx86\_64, arm64Quick start
-----------

[](#quick-start)

```
use Goplasmatic\Datalogic\Engine;

$engine = new Engine();
echo $engine->apply('{"+":[1,2]}', '{}');  // "3"
```

Rules, data, and results cross the boundary as JSON strings; use `json_encode` / `json_decode` at the edges.

Compile once, evaluate many
---------------------------

[](#compile-once-evaluate-many)

Compile the rule once when you'll evaluate it against many data inputs:

```
$engine = new Engine();
$rule = $engine->compile('{"var":"x"}');
foreach ([1, 2, 3] as $x) {
    echo $rule->evaluate(json_encode(['x' => $x])), "\n";
}
```

`Engine` and compiled `Rule` objects carry no per-call state: build and compile once per process and reuse them across requests. Sessions (below) hold a mutable arena, so give each evaluation loop its own.

Data handles (parse once, evaluate many)
----------------------------------------

[](#data-handles-parse-once-evaluate-many)

When one payload feeds many evaluations, parse it once into a `DataHandle` and pass the handle wherever a JSON string is accepted — the per-call JSON parse disappears entirely:

```
use Goplasmatic\Datalogic\DataHandle;

$data = new DataHandle('{"user":{"age":42,"plan":"pro"}}');
$rule->evaluate($data);              // same result as the string overload
$session->evaluate($rule, $data);    // hot path: zero parse work per call
```

Handles are immutable and engine-independent — one handle can feed rules compiled by different engines, any number of times (evaluation never consumes it). The native memory is released when the object is GC'd, or eagerly via `close()`; `allocatedBytes()` reports the handle's resident size.

Typed evaluations
-----------------

[](#typed-evaluations)

Sessions can return native PHP scalars instead of JSON strings — handy for predicates (feature flags, routing) where decoding JSON per call is pure overhead. All four take a compiled `Rule` and a `DataHandle`:

```
$session->evaluateBool($rule, $data);    // bool   — strict: JSON true/false only
$session->evaluateInt($rule, $data);     // int    — exact integers only
$session->evaluateFloat($rule, $data);   // float  — any JSON number
$session->evaluateTruthy($rule, $data);  // bool   — JSONLogic truthiness, never mismatches
```

The strict variants throw `EvaluateException` with `$errorType === "TypeMismatch"` when the result is of any other type; `evaluateTruthy` collapses any result through the engine's configured truthiness rules (the same coercion `if`/`and`/`or` apply).

Batch evaluation
----------------

[](#batch-evaluation)

Evaluate one rule against many payloads (`evaluateBatch`) or many rules against one payload (`evaluateMany`, the rule-set / feature-flag shape) in a single native call. Results come back in input order; a failed item puts a `BatchItemError` in its slot instead of aborting the other N-1 — item failures never throw:

```
use Goplasmatic\Datalogic\BatchItemError;

$results = $session->evaluateBatch($rule, $handles);   // list in
$results = $session->evaluateMany($rules, $data);      // list in

foreach ($results as $i => $r) {
    if ($r instanceof BatchItemError) {
        error_log("item {$i} failed: {$r->tag}: {$r->message}");
        continue;
    }
    // $r is the item's JSON-string result
}
```

`BatchItemError` exposes `$status` (the raw C-ABI status code), `$tag`(stable engine tag, e.g. `"Thrown"`, `"NaN"`), `$message`, and `$operator` (outermost failing operator, when known).

Sessions (hot loops)
--------------------

[](#sessions-hot-loops)

A `Session` reuses one arena across evaluations and resets it at the start of every call, so peak memory stays bounded:

```
$session = $engine->openSession();
foreach ($inputs as $data) {
    $result = $session->evaluate($rule, $data);
}
```

Native handles are released by PHP's destructor when the wrapper object goes out of scope; every wrapper type also exposes an explicit `close()` for early release.

API surface
-----------

[](#api-surface)

The binding mirrors the Rust engine's [API tier model](https://github.com/GoPlasmatic/datalogic-rs#one-api-shape-every-binding). Every method takes and returns JSON strings.

TierEntry pointUse whenOne-shot`$engine->apply($rule, $data)`Ad-hoc evaluation, one rule + one data shapeEngine + config`new Engine($templating)` / `Engine::builder()…->build()`Templating mode, custom operators, evaluation configCompile once`$engine->compile($rule)` → `$rule->evaluate($data)`Same rule evaluated against many data inputsParse once`new DataHandle($json)` → pass instead of a JSON stringSame payload evaluated by many rules/callsSession`$engine->openSession()` → `$session->evaluate($rule, $data)`Hot loops: amortise arena reset across iterationsTyped`$session->evaluateBool/Int/Float/Truthy($rule, $handle)`Predicates: native scalars, no JSON decode per callBatch`$session->evaluateBatch($rule, $handles)` / `evaluateMany($rules, $handle)`Many evaluations per FFI crossing, per-item errorsTraced`$engine->openTracedSession()` → `$session->evaluate($rule, $data)`Step-by-step debugging; feeds the React debugger`Rule::evaluate` and `Session::evaluate` accept either a JSON string or a `DataHandle`.

Custom operators
----------------

[](#custom-operators)

Register PHP-implemented operators through the builder. Each callback receives the operator's pre-evaluated arguments as a JSON-array string and returns a JSON-value string; throwing signals an evaluation error whose message bubbles back to the caller.

```
$engine = Engine::builder()
    ->addOperator('double', function (string $argsJson): string {
        $args = json_decode($argsJson, true);
        return (string) ((int) $args[0] * 2);
    })
    ->build();
echo $engine->apply('{"double":[21]}', '{}');  // "42"
```

**Built-ins win**: a custom registration of a built-in name (`+`, `if`, `var`, ...) never dispatches at evaluation time; the built-in always runs.

Engine configuration
--------------------

[](#engine-configuration)

`Engine::builder()->setConfigJson($json)` sets the evaluation semantics from a JSON object string: an optional `preset` plus per-field overrides. Unknown keys or values throw `EvaluateException` (error type `ConfigurationError`), so typos fail loudly:

```
$lenient = Engine::builder()
    ->setConfigJson('{"division_by_zero":"return_null"}')
    ->build();
echo $lenient->apply('{"/":[1.5,0]}', '{}');  // "null"

$strict = Engine::builder()
    ->setConfigJson('{"preset":"strict"}')
    ->build();
$strict->apply('{"+":["",1]}', '{}');         // throws: strict rejects non-numeric coercion
```

KeyValues`preset``"default"`, `"safe_arithmetic"`, `"strict"``arithmetic_nan_handling``"throw_error"`, `"ignore_value"`, `"coerce_to_zero"`, `"return_null"``division_by_zero``"return_saturated"`, `"throw_error"`, `"return_null"`, `"return_infinity"``loose_equality_errors``bool``truthy_evaluator``"javascript"`, `"python"`, `"strict_boolean"``numeric_coercion`object of bools: `empty_string_to_zero`, `null_to_zero`, `bool_to_number`, `reject_non_numeric``max_recursion_depth`integer &gt;= 1The `preset` applies first; the remaining keys override individual fields on top of it. Every binding shares this JSON schema and parses it with the same core code, so a config that works here works in the Python, Node, and WASM bindings too. The full semantics of each knob are documented on the Rust crate's [`EvaluationConfig`](https://docs.rs/datalogic-rs/latest/datalogic_rs/struct.EvaluationConfig.html).

Error handling
--------------

[](#error-handling)

Everything the binding throws extends `Goplasmatic\Datalogic\Exception\DatalogicException` (a `RuntimeException`):

ExceptionWhen`ParseException`Malformed rule or data JSON, or an unsupported operator`EvaluateException`Operator failure at runtime, or a rejected engine configThe structured fields ride on the base class as public readonly properties: `$errorType` is the stable engine tag (e.g. `"ParseError"`, `"Thrown"`, `"NaN"`), `$operatorName` the outermost failing operator (e.g. `"+"`), and `$pathJson` the root-to-leaf error path as a JSON array; each is `null` when not applicable.

```
use Goplasmatic\Datalogic\Exception\EvaluateException;

try {
    $engine->apply('{"+":["x",1]}', '{}');  // arithmetic on a non-numeric string
} catch (EvaluateException $e) {
    echo $e->errorType;     // runtime error tag, e.g. "Thrown", "NaN"
    echo $e->operatorName;  // "+"
    echo $e->pathJson;      // JSON-array path through the compiled tree
}
```

Under the hood the binding targets the engine's C ABI **v2**: every fallible native call returns a status code plus an owned error handle, and the binding asserts `datalogic_abi_version() == 2` the first time the library is loaded — a stale native library fails loudly at startup (`RuntimeException`), never mid-request.

Threading
---------

[](#threading)

TypePattern`Engine`Build once per process; reuse across requests`Rule`Compile once per process; reuse across requests`DataHandle`Immutable; share freely across engines/rules`Session`One per evaluation loop; do not sharePHP is single-threaded per request, so `Engine`, `Rule`, `Session`, and `TracedSession` are all safe in that model.

Custom operators use PHP FFI's auto-coercion of PHP callables to C function pointers. The builder retains the callable for the engine's lifetime; releasing the engine releases the pin.

Tracing
-------

[](#tracing)

```
$session = $engine->openTracedSession();
$run = $session->evaluate('{"+":[{"var":"x"},1]}', '{"x":41}');
echo $run->result;             // 42
echo count($run->steps);       // executed node count
```

Same trace envelope as every other binding; the [React debugger](https://github.com/GoPlasmatic/datalogic-rs/tree/main/ui)consumes it directly. `TracedRun` exposes `$result`, `$expressionTree`, `$steps`, `$error`, and `$structuredError` (plus `isSuccess()`); runtime failures surface inside the run rather than as exceptions. Tracing disables the optimizer so every operator appears in the trace: use it for debugging, not hot paths.

Performance
-----------

[](#performance)

Geomean across 50 operator benchmark suites (Apple M2 Pro, median of 3 runs; pairwise shared-suite ratios per the [methodology](https://github.com/GoPlasmatic/datalogic-rs/blob/main/tools/benchmark/BENCHMARK.md)): the native Rust core evaluates at **8.9 ns/op**, 7.9× faster than json-logic-engine (compiled, the fastest JS engine), 30.6× faster than jsonlogic-rs (the closest Rust alternative), and 104.2× faster than the json-logic-js reference implementation. The WASM build under Node measures 901.1 ns geomean (101× native); on Node servers, prefer `@goplasmatic/datalogic-node`.

The PHP FFI boundary adds a small per-call marshalling cost on top of the core numbers.

Preloading (opcache.preload + FFI)
----------------------------------

[](#preloading-opcachepreload--ffi)

By default the binding lazily calls `FFI::cdef` on first use, which works out of the box on the CLI. Production FPM/web SAPIs should use [FFI preloading](https://www.php.net/manual/en/ffi.configuration.php)instead: PHP's default `ffi.enable=preload` forbids runtime `FFI::cdef`outside the CLI, and preloading also moves all header parsing to server start. The package ships a ready-made preload script:

```
; php.ini
opcache.preload=/path/to/vendor/goplasmatic/datalogic/preload.php
opcache.preload_user=www-data
ffi.enable=preload
```

`preload.php` resolves the native library exactly like the runtime loader does (see [Building from source](#building-from-source)), rewrites the `FFI_LIB` line of the bundled header (`src/datalogic-ffi.h`) to that absolute path, and registers the persistent FFI scope `"datalogic"` via `FFI::load`. At request time the binding finds the scope through `FFI::scope("datalogic")` and skips `FFI::cdef` entirely; when no scope is preloaded it falls back to `FFI::cdef` transparently. To pin a specific library build, set the `DATALOGIC_NATIVE_LIB` environment variable before the server starts.

If your application already has a preload script, `require` the package's `preload.php` from it (it is idempotent), or call `\Goplasmatic\Datalogic\Internal\Native::preload()` directly. Power users who manage their own headers can copy `src/datalogic-ffi.h`, hard-code `FFI_LIB` to their library path, and `FFI::load` it themselves — the committed default (`libdatalogic_c.so`) resolves via the OS loader path. The header is also the single source of the cdef declarations (Native.php reads it with the `#define` lines stripped), so the two load paths cannot drift apart.

Building from source
--------------------

[](#building-from-source)

The binding lives in [`bindings/php/`](https://github.com/GoPlasmatic/datalogic-rs/tree/main/bindings/php). The FFI loader searches for the cdylib in order: the `DATALOGIC_NATIVE_LIB` env var, the package's `lib/-/` layout, the in-tree C ABI target dir, then the OS's default loader paths. So a fresh clone needs the C ABI built once:

```
git clone https://github.com/GoPlasmatic/datalogic-rs
cd datalogic-rs/bindings/c && cargo build --release
cd ../php
composer install
vendor/bin/phpunit
```

Learn more
----------

[](#learn-more)

- [datalogic-rs repository](https://github.com/GoPlasmatic/datalogic-rs#readme)
- [Rust crate deep-dive](https://github.com/GoPlasmatic/datalogic-rs/tree/main/crates/datalogic-rs#readme)
- [PHP docs chapter](https://goplasmatic.github.io/datalogic-rs/php.html)
- [Online playground](https://goplasmatic.github.io/datalogic-rs/playground/)
- [JSONLogic specification](https://jsonlogic.com)
- [C ABI internals](https://github.com/GoPlasmatic/datalogic-rs/tree/main/bindings/c#readme)

License
-------

[](#license)

Apache-2.0. See the [main repository](https://github.com/GoPlasmatic/datalogic-rs) for source and contribution guidelines.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance93

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 85.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~23 days

Total

4

Last Release

25d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/207297160?v=4)[Plasmatic Engineering](/maintainers/shankar-gpio)[@shankar-gpio](https://github.com/shankar-gpio)

---

Top Contributors

[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (6 commits)")[![shankar-gpio](https://avatars.githubusercontent.com/u/207297160?v=4)](https://github.com/shankar-gpio "shankar-gpio (1 commits)")

---

Tags

feature-flagsopenfeatureffiflagdbusiness-rulesrules enginejson-logicjsonlogicdatalogicexpression-engine

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/goplasmatic-datalogic/health.svg)

```
[![Health](https://phpackages.com/badges/goplasmatic-datalogic/health.svg)](https://phpackages.com/packages/goplasmatic-datalogic)
```

###  Alternatives

[ylsideas/feature-flags

A Laravel package for handling feature flags

6231.5M5](/packages/ylsideas-feature-flags)[shiny/json-logic-php

A modern, complete PHP implementation of JsonLogic. 601/601 official tests. Zero dependencies. PHP 8.1+.

241.1k](/packages/shiny-json-logic-php)[flagception/flagception-bundle

Feature toggle bundle on steroids.

294.1M](/packages/flagception-flagception-bundle)[open-feature/sdk

PHP implementation of the OpenFeature SDK

42891.0k25](/packages/open-feature-sdk)[friendsofcat/laravel-feature-flag

Feature Flags for Laravel

311.2M](/packages/friendsofcat-laravel-feature-flag)[worksome/feature-flags

A package to manage feature flags in your application

11542.8k](/packages/worksome-feature-flags)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
