PHPackages                             mead-steve/tale - 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. mead-steve/tale

ActiveLibrary

mead-steve/tale
===============

v0.4.0(7y ago)242623[3 issues](https://github.com/meadsteve/Tale/issues)MITPHPPHP &gt;=7.1, &lt;8.0

Since Jun 19Pushed 7y ago1 watchersCompare

[ Source](https://github.com/meadsteve/Tale)[ Packagist](https://packagist.org/packages/mead-steve/tale)[ RSS](/packages/mead-steve-tale/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (4)Dependencies (7)Versions (6)Used By (0)

Tale
====

[](#tale)

[![Build Status](https://camo.githubusercontent.com/b36066457268ab489bfd2345a88899a8e1f1fd862e0eb5de4683bf0975bcebf7/68747470733a2f2f7472617669732d63692e6f72672f6d65616473746576652f54616c652e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/meadsteve/Tale)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/f20b0accca2894e8717ac37a0df8a1ba3bcb9c6ed0e60ea63c172d7ad4ce52b7/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6d65616473746576652f54616c652f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/meadsteve/Tale/?branch=master)

What?
-----

[](#what)

Tale is a small library to help write a "distributed transaction like" object across a number of services. It's loosely based on the saga pattern. A good intro is available on the couchbase blog:

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

[](#installation)

```
composer require mead-steve/tale
```

Example Usage
-------------

[](#example-usage)

An example use case of this would be some holiday booking software broken down into a few services.

Assuming we have the following services: Flight booking API, Hotel booking API, and a Customer API.

We'd write the following steps:

```
class DebitCustomerBalanceStep implements Step
{
    //.. Some constructor logic for initialising the api etc...

    public function execute(CustomerPurchase $state)
    {
        $paymentId = $this->customerApi->debit($state->Amount);
        return $state->markAsPaid($paymentId);
    }

    public function compensate($state): void
    {
        $this->customerApi->refundAccountForPayment($state->paymentId)
    }
```

```
class BookFlightStep implements Step
{
    //.. Some constructor logic for initialising the api etc...

    public function execute(FlightPurchase $state)
    {
        $flightsBookingRef = $this->flightApi->buildBooking(
            $state->Destination,
            $state->Origin,
            self::RETURN,
            $this->airline
        );
        if ($flightsBookingRef=== null) {
            raise \Exception("Unable to book flights");
        }
        return $state->flightsBooked($flightsBookingRef);
    }

    public function compensate($state): void
    {
        $this->customerApi->cancelFlights($state->flightsBookingRef)
    }
```

and so on for any of the steps needed. Then in whatever is handling the user's request a distributed transaction can be built:

```
       $transaction = (new Transaction())
            ->add(new DebitCustomerBalance($user))
            ->add(new BookFlightStep($airlineOfChoice))
            ->add(new BookHotelStep())
            ->add(new EmailCustomerDetailsOfBookingStep())

        $result = $transaction
            ->run($startingData)
            ->throwFailures()
            ->finalState();
```

If any step along the way fails then the compensate method on each step is called in reverse order until everything is undone.

State immutability
------------------

[](#state-immutability)

The current state is passed from one step to the next. This same state is also used to compensate for the transactions in the event of a failure further on in the transaction. Since this is the case it is important that implementations consider making the state immutable.

Tale provides a `CloneableState` interface to help with this. Any state implementing this interface will have its `cloneState` method called before being passed to a step ensuring that steps won't share references to the same state.

```
        class FakeState implements CloneableState
        {
                public function cloneState()
                {
                    return clone $this;
                }
        }

        $stepOne = new LambdaStep(
            function (MyStateExample $state) {
                $state->mutateTheState = "step one"
                return $state;
            }
        );
        $stepTwo = new LambdaStep(
            function (MyStateExample $state) {
                $state->mutateTheState = "step two"
                return $state;
            }
        );
        $transaction = (new Transaction())
            ->add($stepOne)
            ->add($stepTwo);

        $startingState = new MyStateExample();
        $finalState = $transaction->run($startingState)->finalState();
```

In the example above `$startingState`, `$finalState` and `$state` given to both function calls are all clones of each other so changing one won't affect any earlier states.

Testing / Development
---------------------

[](#testing--development)

Contributions are very welcome. Please open an issue first if the change is large or will break backwards compatibility.

All builds must pass the travis tests before merge. Running `./run_tests.sh` will run the same tests as travis.yml but locally.

The dockerfile provides an environment that can execute all the tests &amp; static analysis.

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance18

Infrequent updates — may be unmaintained

Popularity22

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity61

Established project with proven stability

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 ~101 days

Total

4

Last Release

2581d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6f6a1fc7be44918da7cc299d340d1e27b338e2396c2f2ca389857b4e2e197c4b?d=identicon)[MeadSteve](/maintainers/MeadSteve)

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mead-steve-tale/health.svg)

```
[![Health](https://phpackages.com/badges/mead-steve-tale/health.svg)](https://phpackages.com/packages/mead-steve-tale)
```

###  Alternatives

[elgg/elgg

Elgg is an award-winning social networking engine, delivering the building blocks that enable businesses, schools, universities and associations to create their own fully-featured social networks and applications.

1.7k15.7k5](/packages/elgg-elgg)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[api-platform/metadata

API Resource-oriented metadata attributes and factories

223.5M96](/packages/api-platform-metadata)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[flowwow/cloudpayments-php-client

cloudpayments api client

2188.2k](/packages/flowwow-cloudpayments-php-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

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