PHPackages                             remotelyliving/doorkeeper - 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. remotelyliving/doorkeeper

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

remotelyliving/doorkeeper
=========================

A Feature Toggle

2.0.0(5y ago)1636.9k—0%2[4 PRs](https://github.com/remotelyliving/doorkeeper/pulls)MITPHPPHP &gt;=7.4

Since Oct 10Pushed 2y ago5 watchersCompare

[ Source](https://github.com/remotelyliving/doorkeeper)[ Packagist](https://packagist.org/packages/remotelyliving/doorkeeper)[ RSS](/packages/remotelyliving-doorkeeper/feed)WikiDiscussions master Synced today

READMEChangelog (8)Dependencies (9)Versions (12)Used By (0)

[![Build Status](https://camo.githubusercontent.com/e6b24a0c95739195dc4bade59467b8a0a57ebb0a97c098abe29513ec317fd992/68747470733a2f2f7472617669732d63692e6f72672f72656d6f74656c796c6976696e672f646f6f726b65657065722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/remotelyliving/doorkeeper)[![Total Downloads](https://camo.githubusercontent.com/09ffa4213db6fddc493232f038a1459ca5838b8cb75dedaad5351397d4100614/68747470733a2f2f706f7365722e707567782e6f72672f72656d6f74656c796c6976696e672f646f6f726b65657065722f646f776e6c6f616473)](https://packagist.org/packages/remotelyliving/doorkeeper)[![Coverage Status](https://camo.githubusercontent.com/0aa305eb25a74c9237d5760e91dfd61e2860e43d939c77ca6076966b83ef8ce6/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f72656d6f74656c796c6976696e672f646f6f726b65657065722f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/remotelyliving/doorkeeper?branch=master)[![License](https://camo.githubusercontent.com/b8c102835edff64c320c8276b028727f899e0033909429f1745d0b1b6415a94e/68747470733a2f2f706f7365722e707567782e6f72672f72656d6f74656c796c6976696e672f646f6f726b65657065722f6c6963656e7365)](https://packagist.org/packages/remotelyliving/doorkeeper)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/9f1cd21dabe8fa7808f4501f6877fcce6875b8117312a1d4f2fcb3b79c9912c2/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f72656d6f74656c796c6976696e672f646f6f726b65657065722f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/remotelyliving/doorkeeper/?branch=master)

Doorkeeper: a dynamic feature toggle
====================================

[](#doorkeeper-a-dynamic-feature-toggle)

### The Birth of a Feature Toggle

[](#the-birth-of-a-feature-toggle)

> Picture the scene. You're on one of several teams working on a sophisticated town planning simulation game. Your team is responsible for the core simulation engine. You have been tasked with increasing the efficiency of the Spline Reticulation algorithm. You know this will require a fairly large overhaul of the implementation which will take several weeks. Meanwhile other members of your team will need to continue some ongoing work on related areas of the codebase. You want to avoid branching for this work if at all possible, based on previous painful experiences of merging long-lived branches in the past. Instead, you decide that the entire team will continue to work on trunk, but the developers working on the Spline Reticulation improvements will use a Feature Toggle to prevent their work from impacting the rest of the team or destabilizing the codebase.

### Enter Doorkeeper (if you can)

[](#enter-doorkeeper-if-you-can)

There are a few feature toggle frameworks and libraries out there already. And many of them are fine. Doorkeeper was born our of a previous experience with one and wishing what it could be.

### Dynamic Usage

[](#dynamic-usage)

Doorkeeper is storage agnostic, and has a few helpers to help translate what you decide to persist and how you want to load it. But however you choose to setup its Feature Set (features + rules), you can toggle your feature on and off by changing that configuration.

### Installation

[](#installation)

`composer require remotelyliving/doorkeeper`

### Wiring it up

[](#wiring-it-up)

```
// Requestors are the actor requesting access to a new feature
// They have several forms of identity you can initialize them
$requestor = ( new Requestor() )
    ->withUserId($user->getId())
    ->withRequest($request)
    ->withIpAddress('127.0.0.1')
    ->withEnvironment('STAGE')
    ->withStringHash('someArbitraryThingMaybeFromTheQueryString');

// A Feature Set has Features that have Rules
$featureSet = $reatureSetRepository->getSet();

// Doorkeper takes in a Feature Set and an audit log if you want to log access results
$doorkeeper = new Doorkeeper($featureSet, $logger);

// Set an app instance bound requestor here or pass one to Doorkeeper::grantsAccessToRequestor('feature', $requestor) later
$doorkeeper->setRequestor($requestor);
```

### Usage

[](#usage)

```
if ($doorkeeper->grantsAccessTo('some.new.feature')) {
    return $this->doNewFeatureStuff();
}

// If you want to bypass the instance Requestor that was set and create another use Doorkeeper::grantsAccessToRequestor()
// This is useful for more stateful applications

$otherRequestor = (new Requestor()))->withUserId(123);

if ($doorkeeper->grantsAccessToRequestor('some.new.feature', $otherRequestor)) {
    return $this->doNewFeatureStuff();
}
```

***Setting a Requestor is not neccessary. It is only needed if you want to use rules that are evaluated against a specific requestor***

### Requestor

[](#requestor)

A Requestor is the one asking to access the feature. They must pass the Doorkeeper's strict house rules to enter. To see if a requestor is allowed access, they must present Identifications.

`RemotelyLiving\Doorkeeper\Identifications`

- HttpHeader - based on a `doorkeeper` header present in a request.
- IntegerId (user id) - Based on the logged in user id
- IpAddress - based on the Requestor's ip address
- StringHash - Some flexibility here. Set it to whatever you want.
- Environment - Based on the app environment the Requestor is in.
- PipedComposite = A string of pipe delimited values used to make a composite key id

The Requestor is immutable. It should not be changed anywhere in the call stack. That would produce less than consistent results depending on where the query takes place.

The Requestor is best wired up and set in a service container. There are several convenience methods to set identities.

### Rules

[](#rules)

`RemotelyLiving\Doorkeeper\Rules`

There are several types of Rules to use when defining access to a feature

- Environment: can be set with the environment name that the feature is available in
- HttpHeader: matches a specific value from a custom `doorkeeper` header you can choose to send. \*The request identification on a Requestor must be registered to have a positive match
- IpAddress: this is a specific IP address the feature is available to. Helpful for in-office access. \*It only works if the Requestor has an IpAddress identification registered
- Percentage: a percentage of requests to allow through to the feature
- Random: chaos monkey. This rule randomly allows access.
- StringHash: this is an arbitrary string. It works well for request query params, username, etc. \*This only works if a StringHash identification is set on the Requestor
- TimeAfter: allows for access to a feature only *after* the set time on the rule.
- TimeBefore: allows for access to a feature only *before* the set time of the rule.
- UserId: this rule allows for specific user access to a feature. \*User Id only works if the Request has a user id identification registered to them
- PipedComposite: allows for a pipe delimited composite key value
- RuntimeCallable: pass in a callable that returns true/false. You'll have access to the requestor as a parameter, but be forewarned that storing this one is not possible (i.e. to a database)

### Prerequisistes

[](#prerequisistes)

Rules can be dependant on other rules for any other feature.

```
// create a time range
$timeBeforeRule->addPrerequisite($timeAfterRule);

// create a user id rule only for prod
$userIdRule->addPrerequisite($prodEnvironmentRule);

// add another prereq
$userIdRule->addPrerequisite($ipAddressRule);

// etc.
```

That prerequisite must be satisfied before the other rule is evaluated.

### Feature

[](#feature)

`RemotelyLiving\Doorkeeper\Features`

A Feature is what a Requestor is asking for by name. It can have 0-n Rules around it. A Feature has a top level on/off switch called `enabled` that can bypass any rules.

```
// $enabled (true/false), $rules (\RemotelyLiving\Doorkeeper\Rules\RuleInterface[])
$feature = new Feature('some.new.feature', $enabled, $rules);
```

Doorkeeper gets the rules from a feature and evaluates them. If they require a specific Identification Doorkeeper looks into the Requestor to see if they have the right Identifications required by a rule

If nothing satisfies the feature rules the default is to deny access.

***The first rule to be satisfied grants access***

Keep that in mind when setting rules up on a feature.

If you say "only this ip address is allowed, but also the DEV environment."

All requests with that ip address OR in the DEV environment will be allowed.

But that means that in ANY environment, that ip address will be allowed.

The proper way to setup exclusions would be to set the ip address rule as a prerequisite rule to the environment one. That would then stipulate that only this ip address in this environment can access.

A Feature with no rules simply relies on the `enabled` field to tell Doorkeeper if it's on or off

### Feature Set

[](#feature-set)

A feature set object is the bread and butter of Doorkeeper. It's a collection of Features and rules and makes for easy caching. It is the complete set of what defines access to features.

### Feature Set Repository and Caching

[](#feature-set-repository-and-caching)

`Remotelyliving\Doorkeeper\Features`

Obviously something that fires up for every request that uses dynamic config data can be costly.

If you're using memcached or redis and a PSR6 cache library. you can cache and retrieve the Feature Set in the `Features\SetRepository`.

You can build the Feature Set from arrays using the `Features\Set::createFromArray()`

That array can come from anywhere: relational database, cache, config, xml, whatever.

Checkout that factory method to see the schema of the array that needs to be passed in.

How you choose to persist Features is up to you. But there are two things you're responsible for if using the `Features\SetRepository`

1. Clearing the cache when any member of a Feature Set is changed via `SetRepository::deleteFeatureSet()`
2. Providing a service that can provide a hydrated Feature Set to the `get('some.new.feature', $featureSetProvider)` method

Doorkeeper also has a runtime cache that caches answers in memory to help as well. For persistent applications you'll need to call `Doorkeeper::flushRuntimeCache()`any time a Rule or Feature is updated.

### Logging

[](#logging)

Doorkeeper comes with a friendly log processor that can pass on filtered or unfiltered info about a Requestor. This is incredibly helpful when debugging. When paired with a Request-Id (or something like that) in the log context debugging a user's code patch can be very easy.

###  Health Score

38

—

LowBetter than 86% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity35

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity70

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

Recently: every ~209 days

Total

9

Last Release

2149d ago

Major Versions

1.5.0 → v2.x-dev2020-05-02

PHP version history (2 changes)1.0.0PHP &gt;=7.0.0

v2.x-devPHP &gt;=7.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/15091ba12470e0054da94a253c264751ef495c9897c59ea6837e986b31606e63?d=identicon)[remotelyliving](/maintainers/remotelyliving)

---

Tags

feature-flagsfeature-togglefeature-togglesfeature flagDoorkeeper

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/remotelyliving-doorkeeper/health.svg)

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

###  Alternatives

[patchlevel/event-sourcing

A lightweight but also all-inclusive event sourcing library with a focus on developer experience

198256.8k5](/packages/patchlevel-event-sourcing)[fisharebest/webtrees

webtrees online genealogy

72910.3k6](/packages/fisharebest-webtrees)[oat-sa/lib-lti1p3-core

OAT LTI 1.3 Core Library

35327.1k9](/packages/oat-sa-lib-lti1p3-core)[heptacom/heptaconnect-core

HEPTAconnect core processes

1224.2k3](/packages/heptacom-heptaconnect-core)[heptacom/heptaconnect-bridge-shopware-platform

HEPTAconnect package to support a runtime in a shopware platform project

1024.6k2](/packages/heptacom-heptaconnect-bridge-shopware-platform)[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)
