PHPackages                             vaened/php-delta-orchestrator - 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. vaened/php-delta-orchestrator

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

vaened/php-delta-orchestrator
=============================

Framework-agnostic partial update orchestration engine based on real deltas

v0.5.2(3w ago)272↑33.3%MITPHPPHP ^8.2CI passing

Since Apr 27Pushed 3w agoCompare

[ Source](https://github.com/vaened/php-delta-orchestrator)[ Packagist](https://packagist.org/packages/vaened/php-delta-orchestrator)[ RSS](/packages/vaened-php-delta-orchestrator/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (1)Versions (8)Used By (0)

PHP Delta Orchestrator
======================

[](#php-delta-orchestrator)

[![Tests](https://github.com/vaened/php-delta-orchestrator/actions/workflows/test.yml/badge.svg)](https://github.com/vaened/php-delta-orchestrator/actions/workflows/test.yml)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)

**php-delta-orchestrator** is a library for orchestrating partial updates by comparing incoming input against the current state, producing a [`Delta`](src/Delta.php) and executing [`Action`](src/Action.php) instances only when appropriate.

```
// Patch + current state
$startDate = Field::from(
    // incoming patch vs current value
    patch  : $payload->startDate,
    current: $availability->startDate,
);

$endDate = Field::from(
    patch  : $payload->endDate,
    current: $availability->endDate,
);

$orchestrator = new Orchestrator();

$orchestrator->register(new Action(
    fields: [$startDate, $endDate],
    // runs only if the action applies, contract is satisfied, and there is an effective delta
    apply: function (Field $startDate, Field $endDate): void {
        // use $field->delta(), $field->value(), $field->current()
    },
));

$result = $orchestrator->execute();
```

Installation
------------

[](#installation)

Delta Orchestrator requires PHP 8.2 or higher and can be installed via Composer:

```
composer require vaened/php-delta-orchestrator
```

Problem it solves
-----------------

[](#problem-it-solves)

### Traditional approach

[](#traditional-approach)

When handling partial updates, code tends to quickly degrade into scattered conditional logic:

- checking whether a field is present in the input,
- comparing it with the current value,
- deciding whether to execute business logic,
- avoiding unnecessary operations when nothing has changed.

This usually leads to nested conditionals, duplicated comparison logic, and implicit rules spread across the application layer.

The core issue is that this approach mixes in the same place:

- input handling,
- change detection,
- action execution.

### This library’s approach

[](#this-librarys-approach)

An explicit flow is introduced where each responsibility is clearly separated:

- [`PatchValue`](src/Patch/PatchValue.php) models input presence and normalization,
- [`Field`](src/Field.php) evaluates changes against the current state,
- [`Delta`](src/Delta.php) represents an effective transition,
- [`Action`](src/Action.php) defines when and how to execute logic.

Conceptual model
----------------

[](#conceptual-model)

The library organizes the flow of a partial update into explicit steps:

 ```
flowchart LR
    A["Patch + current state"] --> B["Field(presence + comparison)"]
    B --> C{"Does the action apply?(when)"}
    C -- "No" --> X["Skip"]
    C -- "Yes" --> D{"Is contract satisfied?(behaviors)"}
    D -- "No" --> Z["Throw exception"]
    D -- "Yes" --> E{"Is there an effective delta?"}
    E -- "No" --> X
    E -- "Yes" --> G["apply()"]
```

      Loading Usage
-----

[](#usage)

The following section shows how to apply the flow defined in the conceptual model.

### 1) Model patchable input

[](#1-model-patchable-input)

You can represent partial input in two ways.

#### Option A: Typed command

[](#option-a-typed-command)

```
final readonly class UpdateAvailabilityCommand
{
    public function __construct(
        public DateTimeImmutablePatchValue $startDate,
        public DateTimeImmutablePatchValue $endDate,
    ) {}
}
```

#### Option B: From array using [`PatchInput`](src/Patch/PatchInput.php)

[](#option-b-from-array-using-patchinput)

```
$input = new PatchInput(
    input: $request->all(),
);

$startDate = $input->dateTimeImmutable('start_date');
$endDate   = $input->dateTimeImmutable('end_date');
```

`PatchValue` represents:

- presence (`isPresent()`)
- incoming value (`value()`), potentially normalized

### 2) Define fields

[](#2-define-fields)

You connect the patch with the current state using [`Field::from()`](src/Field.php). Each `patch` represents a `PatchValue`, not the final value, so the incoming value may differ in type from the current state.

```
$startDate = Field::from(
    patch  : $payload->startDate,
    current: $availability->startDate,
);
```

You can optionally define a comparator:

```
$endDate = Field::from(
    patch  : $payload->endDate,
    current: $availability->endDate,
)->using(comparator: DateTimeComparator::create());
```

You can also transform the incoming patch value before comparison and action execution:

```
$name = Field::from(
    patch  : $payload->name,
    current: $current->name,
)
    ->transform(static fn(string $value): string => strtolower(trim($value)))
    ->using(comparator: StrictComparator::create());
```

Each [`Field`](src/Field.php) exposes:

- `isPresent()` → whether the field was provided in the patch
- `isChanged()` → whether the field has a real delta against the current value
- `value()` → incoming value
- `current()` → current value
- `effective()` → incoming value when present, otherwise current value
- `changed()` → incoming value when a real change exists, otherwise `null`
- `delta()` → returns the transition (`previous → next`) if a change exists, or `null` otherwise

### 3) Declare actions

[](#3-declare-actions)

You define what should happen when a combination of fields applies through an [`Action`](src/Action.php).

```
$orchestrator->register(new Action(
    fields     : [$startDate, $endDate],
    apply      : function (Field $startDate, Field $endDate): void {
        // call to application/domain service
    },
    when: static fn(Field ...$fields) => any($fields),
    description: 'Update availability period',
));
```

#### Behaviors

[](#behaviors)

Behaviors define the execution contract through [`Required`](src/Bindings/Required.php) and [`Optional`](src/Bindings/Optional.php):

```
fields: [
    $startDate->required(),
    $endDate->optional(),
]
```

- `required()` → the field must provide a usable value
- `optional()` → the field may be absent

#### Activation rule (`when`)

[](#activation-rule-when)

`when` determines whether the action participates in the current patch.

By default, an action applies if **at least one field is present**.

You can define custom rules:

```
when: static fn(Field ...$fields) => all($fields)
```

### 4) Execute orchestrator

[](#4-execute-orchestrator)

```
$result = $orchestrator->execute();
```

The [`Orchestrator`](src/Orchestrator.php) performs:

1. Evaluates `when` (presence-based activation)
2. Validates the contract (`behaviors`)
3. Checks for an effective delta
4. Executes `apply()` if applicable

`execute()` returns an [`ExecutionResult`](src/ExecutionResult.php), so you can react to the run outcome:

It includes totals and execution state (`total`, `executed`, `skipped`) plus helper checks and description-based lookups.

```
if ($result->hasEffects()) {
    // persist / publish events
}
```

### Note on current vs patch values

[](#note-on-current-vs-patch-values)

The library does not automatically build a projected state.

If you need the effective value for a field (patch value when present, otherwise current value), use `effective()`:

```
$start = $startDate->effective();
```

This keeps action code cleaner while preserving explicit field-level behavior.

Rules
-----

[](#rules)

Rules allow you to declaratively define activation conditions (`when`) through helpers in [`src/Rules/functions.php`](src/Rules/functions.php).

### present()

[](#present)

Checks whether a field is present in the patch.

```
present($startDate)
```

### all() and any()

[](#all-and-any)

Allow composing conditions:

```
use function Vaened\DeltaOrchestrator\Rules\all;
use function Vaened\DeltaOrchestrator\Rules\any;

all([$startDate, $endDate]);
any([$startDate, $endDate]);
```

You can also nest rules:

```
all([
    $startDate,
    any([$endDate, $publishedAt]),
]);
```

Activation (`when`)
-------------------

[](#activation-when)

Advanced details on how to define custom activation rules.

```
$action = new Action(
    fields: [$startDate, $endDate],
    when  : static fn(Field ...$fields) => all($fields),
    apply : static function (Field $startDate, Field $endDate): void {
        // ...
    },
);
```

`when` determines whether the action participates in the current patch.

Field
-----

[](#field)

### Comparators

[](#comparators)

Each `Field` compares the incoming value against the current value using a comparator.

#### Default

[](#default)

If none is defined, `StrictComparator` is used.

- compares strictly by type and value,
- compares dates by exact temporal value,
- throws `ComparisonTypeMismatch` if types are not compatible.

#### NumericComparator

[](#numericcomparator)

For numeric values and numeric strings.

```
$quantity = Field::from(
    patch  : $payload->quantity,
    current: $current->quantity,
)->using(comparator: NumericComparator::create());
```

#### DateTimeComparator

[](#datetimecomparator)

For date comparisons with explicit semantics.

```
$startDate = Field::from(
    patch  : $payload->startDate,
    current: $current->startDate,
)->using(comparator: DateTimeComparator::create());
```

#### LooseComparator

[](#loosecomparator)

For cases where intentional loose comparison (`==`) is desired.

```
$value = Field::from(
    patch  : $payload->value,
    current: $current->value,
)->using(comparator: LooseComparator::create());
```

#### ArrayComparator

[](#arraycomparator)

For recursive array comparisons, with support for injecting an item comparator.

```
$settings = Field::from(
    patch  : $payload->settings,
    current: $current->settings,
)->using(comparator: ArrayComparator::create());
```

You can also provide a custom comparator for leaf values:

```
$settings = Field::from(
    patch  : $payload->settings,
    current: $current->settings,
)->using(comparator: ArrayComparator::create(LooseComparator::create()));
```

Patch (input)
-------------

[](#patch-input)

### PatchValue and normalization

[](#patchvalue-and-normalization)

Concrete [`PatchValue`](src/Patch/PatchValue.php) implementations can accept flexible inputs and return normalized values.

```
new IntPatchValue(true, '20')->value();
new BoolPatchValue(true, 'true')->value();
new DateTimeImmutablePatchValue(true, '2026-04-26 10:20:30')->value();
```

This keeps normalization at the input boundary, preventing raw values from leaking into the domain.

Playground
----------

[](#playground)

The repository includes an executable usage scenario located at [`playground/playground.php`](playground/playground.php).

Unlike the snippets in the README, this example brings multiple cases together in a single flow:

- multiple `Action` instances over the same patch,
- combination of `required()` and `optional()`,
- use of `when` to control activation by presence,
- cases with and without effective `delta`,
- use of current values as fallback,
- a contract failure case (`required` with `null`).

The scenario is not intended to be minimal. It deliberately groups more logic than usual to expose different behaviors in a single execution.

### Run

[](#run)

```
make playground
```

Additional documentation
------------------------

[](#additional-documentation)

You can find more details in the source code as well as in the tests located in [`tests/`](tests).

The tests cover different usage scenarios and can serve as additional reference for understanding the library’s behavior.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance94

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~3 days

Total

7

Last Release

25d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/15077850?v=4)[Enea Dhack](/maintainers/vaened)[@vaened](https://github.com/vaened)

---

Top Contributors

[![vaened](https://avatars.githubusercontent.com/u/15077850?v=4)](https://github.com/vaened "vaened (97 commits)")

---

Tags

phpdeltapatchorchestratorapplication-layerpartial-update

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/vaened-php-delta-orchestrator/health.svg)

```
[![Health](https://phpackages.com/badges/vaened-php-delta-orchestrator/health.svg)](https://phpackages.com/packages/vaened-php-delta-orchestrator)
```

###  Alternatives

[imanghafoori/laravel-anypass

A minimal yet powerful package to help you in development.

21422.6k](/packages/imanghafoori-laravel-anypass)

PHPackages © 2026

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