PHPackages                             minfrastructure/chains - 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. minfrastructure/chains

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

minfrastructure/chains
======================

Build Different type of chains with optional filtering

v0.1.0(4mo ago)00MITPHPPHP &gt;=8.2

Since Feb 11Pushed 4mo agoCompare

[ Source](https://github.com/minfrastructure/chains)[ Packagist](https://packagist.org/packages/minfrastructure/chains)[ RSS](/packages/minfrastructure-chains/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)DependenciesVersions (2)Used By (0)

Chains
======

[](#chains)

A PHP library for building event-driven execution chains with hooks, callbacks, policies, and a rich result system. Zero dependencies.

Chains lets you wrap any operation in a structured pipeline of phases and hooks, so that internal and external code can intercept, validate, transform, or react to every step — without tight coupling.

Current library focuses on reusability. You can implement once and customize the class behavior on according to the business needs.

Requirements
------------

[](#requirements)

- PHP 8.2+

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

[](#installation)

```
composer require minfra/chains
```

Quick Start
-----------

[](#quick-start)

```
use Minfra\Chains\EventExecutor;
use Minfra\Chains\EventInterface;
use Minfra\Chains\Result;
use Minfra\Chains\Meta;
use Minfra\Chains\BasicResultInterface;

//A simple example with real world application.
class Product {
    protected string $price;

    function setPrice(string $price): BasicResultInterface {
        return EventExecutor::run($this, 'set_price', Meta::make(price: $price), [
        EventInterface::ON_MID => [$this, '_set_price'], // or a single hook (any callable)
]);
    }

    protected function _set_price(Meta $meta): void {
        $meta->this->price = $meta->price;  // $meta->this refers to the current object.
        // We could also do `return true;` or do `Result::true($meta)` or `Result::true()` or `Result::true()`

        // when returning true or returning null(or no return at all). it automatically passes the $meta to the next phase.
    }
}
```

```
// The above example, but even simpler
class Product {
    protected string $price;

    function setPrice(string $price): BasicResultInterface {
        return EventExecutor::run($this, 'set_price', ...CommonEvents::setter(['price' => $price]));  // CommonEvents provides wheels for working with properties
    }
}
```

```
// The same as above example, but with more hooks[pipeline]
class Product {
    protected string $price;

    function setPrice(string $price): BasicResultInterface {
        list($meta, $setter_cb) = CommonEvents::setter(['price' => $price]);

        return EventExecutor::run($this, 'set_price', $meta, [
            EventInterface::ON_BEFORE => [[$this, "_ensure_correct_price"], [$this, "_ensure_correct_decimal_price"]],
            EventInterface::ON_MID    => [[$this, "_set_price"]],
            EventInterface::ON_OK     => fn(Meta $meta) => error_log("price is: {$meta->price}"),
        ]);
    }

    protected function _ensure_correct_price(Meta $meta): BasicResultInterface|true {
        if (!is_numeric($meta->price)) {
            return Result::false("invalid_price", $meta);  // Or simply: Result::false("invalid_price");
        }

        return true;  // when returning true. it automatically passes the $meta to the next phase.
    }

    protected function _ensure_correct_decimal_price(Meta $meta): void {
        $meta->price = bcmath($meta->price, '1', 2);
    }

    protected function _set_price(Meta $meta): void {
        $meta->this->price = $meta->price;  // $meta->this refers to the current object.
        // We could also do `return true;` or do `Result::true($meta)` or `Result::true()` or `Result::true()`

        // when returning null(or no return at all). it automatically passes the $meta to the next phase.
    }
}
```

```
// The same as above example, but make it pluggable(register external callbacks)
use Minfra\Chains\EventExecutor;
use Minfra\Chains\EventInterface;
use Minfra\Chains\CallbackTrait;
use Minfra\Chains\CommonEvents;
use Minfra\Chains\Result;
use Minfra\Chains\BasicResultInterface;
use Minfra\Chains\Meta;

class Product implements \Minfra\Chains\CallbackInterface {
    use CallbackTrait;

    protected string $price;
    protected int $type;

    const EVENT_ON_SET_TYPE = "set_type";
    const EVENT_ON_GET_TYPE = "get_type";

    const EVENT_ON_SET_PRICE = "set_price";
    const EVENT_ON_GET_PRICE = "get_price";

    function setType(int $type): BasicResultInterface {
        return EventExecutor::run($this, static::EVENT_ON_SET_TYPE, ...CommonEvents::setter(['type' => $type]));
    }

    function getType(): Result {
        $type = $this->type ?? null;
        return EventExecutor::run($this, static::EVENT_ON_GET_TYPE, ...CommonEvents::getter('type', $type));
    }

    function setPrice(string $price): BasicResultInterface {
        list($meta, $setter_cb) = CommonEvents::setter(['price' => $price]);

        return EventExecutor::run($this, static::EVENT_ON_SET_PRICE, $meta, [
            EventInterface::ON_BEFORE => [[$this, "_ensure_correct_price"], [$this, "_ensure_correct_decimal_price"]],
            EventInterface::ON_MID    => [$setter_cb],
            EventInterface::ON_OK     => fn(Meta $meta) => error_log("price is: {$meta->price}"),
        ]);
    }

    function getPrice(): Result {
        $price = $this->price ?? null;
        return EventExecutor::run($this, static::EVENT_ON_GET_PRICE, ...CommonEvents::getter('price', $price));
    }

    protected function _ensure_correct_price(Meta $meta): BasicResultInterface|true {
        if (!is_numeric($meta->price)) {
            return Result::false("invalid_price", $meta);  // Or simply: Result::false("invalid_price");
        }

        return true;  // when returning true. it automatically passes the $meta to the next phase.
    }

    protected function _ensure_correct_decimal_price(Meta $meta): BasicResultInterface|true {
        // $meta->price = bcmath($meta->price, '1', 2);
        return true;
    }
}

$product = new Product();
$product->appendCallback((new \Minfra\Chains\CallbackEntry($product::EVENT_ON_SET_PRICE, \Minfra\Chains\CallbackEntryInterface::EVENT_PRE))->setCallback(function(Meta $meta) {
    /** @var Product $product */
    $product = $meta->this;
    if ($product->getType()->data()->type === 222) {
        if ($meta->price > "20") {
            return Result::false("invalid_product_222_issue", ['reason' => 'cannot be higher than 20']);
            // or if you prefer using meta: return Result::false("invalid_product_222_issue", Meta::make(reason: 'cannot be higher than 20'));
        }
    }

    return true;
}));
$product->appendCallback((new \Minfra\Chains\CallbackEntry($product::EVENT_ON_SET_PRICE, \Minfra\Chains\CallbackEntryInterface::EVENT_POST))->setCallback(function(Meta $meta) {
    var_dump("I got the price: {$meta->price}. let's do something with it.");
}));

$product->setType(222);
var_dump($product->setPrice('21')->ok()); // false
var_dump($product->setPrice('19')->ok()); // true
```

> > You can run the latest example [here](examples/09_model_lifecycle2.php)

How It Works
------------

[](#how-it-works)

Every call to `EventExecutor::run()` executes a fixed sequence of phases:

```
ON_BEFORE → PRE callbacks → ON_MID → POST callbacks → ON_AFTER
                                                          │
                         ┌────────────────────────────────┘
                         ▼
              ON_INCOMPLETE  (if not completed)
              ON_DONE        (if done flag set)
              ON_OK          (if ok)
              ON_FAILED      (if not ok)

```

- **Hooks** are functions, methods or closures you pass to `EventExecutor::run()`, keyed by phase.
- **Callbacks** are externally registered on the instance itself (via `CallbackInterface`) and run at the PRE and POST stages. think of them as plugin.
- **Results** flow through the chain. Each step can inspect, modify, or replace the current result.
- **Policies** control what happens on failure, incomplete results, retries, and early termination.

Key Concepts
------------

[](#key-concepts)

### Result

[](#result)

The value object that flows through the chain. Carries state flags and a data payload.

```
Result::true($data, completed: true);          // success
Result::false('error_code', $data);            // failure
Result::incomplete('pending', $data);          // not yet done
Result::from($otherResult, ok: false);         // clone with overrides

$result->ok();         // bool — success?
$result->data();       // mixed — the payload
$result->status();     // string — error/status code
$result->completed();  // bool — work fully done?
$result->done();       // bool — stop the chain early?
$result->again();      // bool — retry this step?

```

### Callback Return Contract

[](#callback-return-contract)

Every hook/callback receives `($data, $phase, $executor, $result)` and returns:

Return valueEffect`null` or `true`Continue with the current result unchanged`BasicResultInterface`Replace the current result### CommonEvents Helpers

[](#commonevents-helpers)

Ready-made `[Meta, callable]` pairs for property operations on objects — including protected/private properties. Use the spread operator to pass them to `EventExecutor::run()`:

```
// Set protected properties
EventExecutor::run($obj, 'set_name', ...CommonEvents::setter(['name' => 'Alice']));

// Read & validate
EventExecutor::run($obj, 'get_name', ...CommonEvents::getter('name', $value));

// Array operations
EventExecutor::run($obj, 'add_tag',  ...CommonEvents::adding('tags', ['php']));
EventExecutor::run($obj, 'append',   ...CommonEvents::append('logs', 'auth', ['entry']));

// Remove & reset
EventExecutor::run($obj, 'remove',   ...CommonEvents::removing(['email']));
EventExecutor::run($obj, 'clear',    ...CommonEvents::cleanup('tags', []));
```

### CallbackInterface

[](#callbackinterface)

Objects that implement `CallbackInterface` (via `CallbackTrait`) can register their own callbacks. The executor picks them up automatically:

```
class Order implements CallbackInterface {
    use CallbackTrait;

    function __construct() {
        $this->appendCallback(
            (new CallbackEntry('save', 'pre'))
                ->setCallback(function (Meta $d) {
                    // validate before save
                    return true;
                }),
            (new CallbackEntry('save', 'post'))
                ->setCallback(function (Meta $d) {
                    // notify after save
                    return Result::true($d, completed: true);
                }),
        );
    }
}
```

### Execution Policy

[](#execution-policy)

`DefaultEventExecutorPolicy` controls chain behavior:

```
$policy = new DefaultEventExecutorPolicy(
    jump_on_failure: true,            // continue chain after a failed step
    jump_on_incomplete: false,        // stop on incomplete results
    keep_running_on_done: false,      // stop when done flag is set
    ignore_failed_data_on_jump: true, // don't forward failed data
    mark_incomplete_as_failed: true,  // treat incomplete as failure
    again_limit: 1,                   // max retries per callback
);

EventExecutor::run($instance, 'event', policy: $policy, ...);
```

### Source Interfaces

[](#source-interfaces)

An instance can implement these optional interfaces so the executor automatically queries it for configuration — no extra arguments needed:

InterfaceMethodPurpose`EventSourceDataInterface``getEventData($name, $data)`Provide custom event data`EventSourceHooksInterface``getEventHooks($name, $hooks)`Inject hooks automatically`EventSourcePolicyInterface``getEventExecutorPolicy($name)`Provide per-event policy### Retry Mechanism

[](#retry-mechanism)

A callback can request a retry by calling `setAgain()` on the result. The executor triggers `ON_AGAIN`, then re-runs the callback up to `againLimit` times:

```
function (Meta $d) {
    if ($transientFailure) {
        return Result::true($d)->setAgain();
    }
    return Result::true($d, completed: true);
}
```

Examples
--------

[](#examples)

The [`examples/`](examples) directory contains runnable scripts covering every feature:

FileTopic`01_minimal.php`Simplest possible usage`02_hooks_and_phases.php`Full phase lifecycle, terminal hooks`03_results.php`Result creation, states, `from()``04_common_events.php`getter/setter/adding/removing/cleanup, safe reflection`05_callbacks.php`CallbackInterface, PRE/POST stages, priority`06_policy.php`All policy options demonstrated`07_retry.php`again mechanism, limits, abort`08_source_interfaces.php`EventSourceData/Hooks/Policy interfaces`09_model_lifecycle.php`Full domain model with event-driven getters/setters```
# Run all examples
php examples/run_all.php

# Run a single example
php examples/01_minimal.php
```

Tutorial
--------

[](#tutorial)

See [TUTORIAL.md](TUTORIAL.md) for a step-by-step walkthrough from basic usage to building a full domain model with event-driven property access.

###  Health Score

31

—

LowBetter than 66% of packages

Maintenance75

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 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

Unknown

Total

1

Last Release

141d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2dcdbf33695e1848ca525b74e579e426cc34b765687650dea7bc812fa3c02532?d=identicon)[mohammadhzp](/maintainers/mohammadhzp)

---

Top Contributors

[![mohammadhzp](https://avatars.githubusercontent.com/u/6490286?v=4)](https://github.com/mohammadhzp "mohammadhzp (15 commits)")

### Embed Badge

![Health badge](/badges/minfrastructure-chains/health.svg)

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

PHPackages © 2026

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