PHPackages                             epignosis/flipster - 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. epignosis/flipster

ActiveLibrary

epignosis/flipster
==================

An OpenFeature toolkit for PHP: inject any provider, wrap it in a circuit breaker, and fall back to configured defaults when it degrades.

v1.0.0(2w ago)01↑2900%MITPHPPHP ^8.1CI failing

Since Aug 5Pushed 2w agoCompare

[ Source](https://github.com/epignosis/flipster)[ Packagist](https://packagist.org/packages/epignosis/flipster)[ RSS](/packages/epignosis-flipster/feed)WikiDiscussions main Synced today

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

Flipster
========

[](#flipster)

[![CI](https://github.com/epignosis/flipster/actions/workflows/ci.yml/badge.svg)](https://github.com/epignosis/flipster/actions/workflows/ci.yml)[![PHP](https://camo.githubusercontent.com/c9f3bb4faea7a252776f7d448df21ac8e697ae4f4bff8f920bb4daf118692acc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e31253230254532253830253933253230382e352d373737626234)](https://www.php.net/supported-versions.php)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

An [OpenFeature](https://openfeature.dev) toolkit for PHP. Inject any provider, wrap it in a circuit breaker, and serve declared defaults when it degrades.

Flipster does not replace the OpenFeature SDK or invent a competing flag API. It closes three gaps that every team otherwise solves again:

- **Wiring** — the SDK's idiomatic entry point is a process-global singleton. Flipster gives you a narrow interface to inject instead.
- **Blast radius** — a flag provider is a network call on your hot path. Flipster puts a circuit breaker in front of it.
- **Testability** — the failure paths are the ones worth testing. Flipster ships doubles for them.

```
composer require epignosis/flipster
```

Thirty seconds
--------------

[](#thirty-seconds)

```
use Epignosis\Flipster\Flipster;

$provider = ...; // Setup the actual provider
$breaker = ...; // Setup the circuit breaker

$flags = Flipster::for($provider)
    ->withDefaults([
        'new-checkout'     => false,
        'checkout-variant' => 'control',
        'rate-limit-rpm'   => 100,
    ])
    ->withBreaker($breaker, 'flags')
    ->evaluator();

// Inject $flags as a FlagEvaluator. Domain code sees nothing else.
if ($flags->isEnabled('new-checkout')) {
    // ...
}
```

Note what the call site does **not** contain a default value. Defaults are declared once, in one place, so a call site cannot disagree with configuration.

A runnable version of the above — no backend required — is [`examples/in-memory.php`](examples/in-memory.php):

```
./dev example in-memory
```

What happens when the provider breaks
-------------------------------------

[](#what-happens-when-the-provider-breaks)

This is the part worth reading carefully, because it is the reason the library exists.

SituationValue served`reason`Provider answersthe live value, **untouched**as the provider reportedProvider errors, flag declaredthe **declared default**`ERROR`Circuit open, flag declaredthe **declared default** (provider not called)`ERROR`Degraded, flag not declaredthe caller's default`ERROR`Degraded, declared with the wrong typethe caller's default, warning logged`ERROR`Two things never happen: an evaluation does not throw because a provider is unwell, and a live value is never overridden by a declaration.

Here is that table as observed output. `examples/flagd.php` runs against a real flagd instance which is stopped part-way through:

```
 9. new-checkout=true  checkout-variant=blue     rate-limit-rpm=500    false,      // bool
    'variant'        => 'control',  // string
    'rate-limit-rpm' => 100,        // int
    'timeout'        => 30,         // int is accepted for a float flag
    'config'         => ['a' => 1], // array (OpenFeature "object")
])
```

Declarations are validated when the stack is built, not at first use — a malformed map fails your deploy rather than surprising you mid-incident. Rejected: non-string keys, keys with surrounding whitespace, `null`, and objects.

**An undeclared flag throws.** `FlagEvaluator::isEnabled('typo')` raises `UndeclaredFlagException` rather than returning `false`, because a mistyped key that reads as "feature off" is indistinguishable from a deliberately disabled feature, and nobody investigates a feature that looks correctly configured. The message suggests the key you probably meant.

If you want per-call defaults, use the OpenFeature `Client` directly — `Flipster::client()` returns one, and the resilience decorators work identically on that path.

The circuit breaker
-------------------

[](#the-circuit-breaker)

Flipster defines a small `CircuitBreaker` port and ships an adapter over [ackintosh/ganesha](https://github.com/ackintosh/ganesha).

```
use Ackintosh\Ganesha;
use Epignosis\Flipster\CircuitBreaker\GaneshaCircuitBreaker;

$ganesha = Ganesha\Builder::withRateStrategy()
    ->adapter(new Ganesha\Storage\Adapter\Redis($redis))
    ->failureRateThreshold(50)  // percent
    ->minimumRequests(10)       // read docs/breaker.md before choosing this
    ->timeWindow(30)            // seconds
    ->intervalToHalfOpen(10)
    ->build();

$breaker = new GaneshaCircuitBreaker($ganesha);
```

Two things to know before configuring it, both covered in **[docs/breaker.md](docs/breaker.md)**:

- **Redis cannot use the count strategy.** It is the only Ganesha adapter that does not support it, and it is the one you want for sharing state across PHP-FPM workers. So production means the *rate* strategy.
- **The rate strategy will not trip on a quiet service.** It ignores the failure rate until `minimumRequests`have been seen inside `timeWindow`. Set that below the traffic your window genuinely sees, or the circuit never opens however dead the backend is.

A missing flag never trips the breaker. `FLAG_NOT_FOUND` means the provider answered correctly, and counting it would let one mistyped key disable every flag in the system.

Testing your own code
---------------------

[](#testing-your-own-code)

The doubles ship in the runtime package, so your test suite can use them with no extra dependency:

```
use Epignosis\Flipster\Testing\ControllableCircuitBreaker;
use Epignosis\Flipster\Testing\InMemoryProvider;
use Epignosis\Flipster\Testing\RecordingProvider;

$recorder = new RecordingProvider(new InMemoryProvider(['kill-switch' => false]));
$breaker  = new ControllableCircuitBreaker();

$flags = Flipster::for($recorder)
    ->withDefaults(['kill-switch' => true])
    ->withBreaker($breaker, 'flags')
    ->evaluator();

$breaker->open('flags');                        // pin an outage — no thresholds to reach
$flags->isEnabled('kill-switch');               // true, the declared default
$recorder->timesEvaluated('kill-switch');       // 0, the provider was never reached
```

`InMemoryProvider` · `RecordingProvider` · `ThrowingProvider` · `ControllableCircuitBreaker`. See **[docs/testing.md](docs/testing.md)**.

Caching
-------

[](#caching)

Flipster does not cache, at any version. Caching belongs to the provider you selected, which owns the flag data's lifecycle and its invalidation signals; a cache bolted on from outside can only guess with a TTL.

There is a PHP-specific trap here worth knowing about — see **[docs/caching.md](docs/caching.md)**.

Documentation
-------------

[](#documentation)

FileDescription[docs/architecture.md](docs/architecture.md)how the decorators compose, and why[docs/breaker.md](docs/breaker.md)tuning, and the two traps above[docs/testing.md](docs/testing.md)the toolkit in detail[docs/caching.md](docs/caching.md)why this library does not cache[docs/migrating.md](docs/migrating.md)adopting Flipster in a codebase already using the SDKRequirements
------------

[](#requirements)

- PHP 8.1 – 8.5. **8.1 is end-of-life**; it is supported so the library can drop into codebases that have not migrated yet, and you should upgrade.
- `open-feature/sdk ^2.3`, `ackintosh/ganesha ^4.0`, `psr/log`. No concrete provider is bundled.

Contributing
------------

[](#contributing)

Docker is the only prerequisite — no PHP or Composer on your machine.

```
./dev check      # style, static analysis, tests
```

See [CONTRIBUTING.md](CONTRIBUTING.md).

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance96

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity42

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

Unknown

Total

1

Last Release

20d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1826525?v=4)[Epignosis](/maintainers/epignosis)[@epignosis](https://github.com/epignosis)

![](https://avatars.githubusercontent.com/u/76998880?v=4)[Vassilis Poursalidis](/maintainers/vpoursalidis)[@vpoursalidis](https://github.com/vpoursalidis)

---

Top Contributors

[![vpoursalidis](https://avatars.githubusercontent.com/u/76998880?v=4)](https://github.com/vpoursalidis "vpoursalidis (9 commits)")

---

Tags

feature-flagsfeature-togglescircuit breakeropenfeatureresilience

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/epignosis-flipster/health.svg)

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

###  Alternatives

[symfony/cache

Provides extended PSR-6, PSR-16 (and tags) implementations

4.2k382.4M3.7k](/packages/symfony-cache)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.7k39.6k](/packages/matomo-matomo)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

568591.1k63](/packages/ecotone-ecotone)[api-platform/metadata

API Resource-oriented metadata attributes and factories

275.5M254](/packages/api-platform-metadata)[mimmi20/browser-detector

Library to detect Browsers and Devices

49158.4k5](/packages/mimmi20-browser-detector)

PHPackages © 2026

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