This shared PHP library evaluates Statsig feature gates and dynamic configs. Gate and dynamic-config results are cached in Redis.
The Split implementation was removed in March 2026 and is no longer included. Composer supports PHP 7 and 8; the local test container uses PHP 7.0.
The repo comes with a PHP 7.0 docker container that includes xdebug and a handful of other nice to have PHP libraries/extensions enabled.
To run this locally, start by building the Docker image:
$ make buildYou can then install the Composer dependencies:
$ make installOnce the composer dependencies have been installed, you can run the unit tests:
$ make testThere are also Make commands to simply bring up/down the docker container and shell into the container:
$ make up
$ make shell
$ make downtry {
$user = new FeatureFlagUser('dealer@dealerinspire.com');
$flags = (new FeatureFlag())
->setUser($user);
if ($flags->exists('my-new-feature')) {
if ($flags->enabled('my-new-feature')) {
// feature exists and is turned on for this user
} else {
// feature is either turned off for this user
}
} else {
// the feature flag does not exist yet
}
} catch (\Carsdotcom\FeatureFlags\Exceptions\InvalidFeatureFlagUserException $exception) {
// the user passed was invalid. This could happen if the user identifier wasn't sent during creation
} catch (\Carsdotcom\FeatureFlags\Exceptions\InvalidFeatureFlagException $exception) {
// if the feature flag we called '$flags->enabled()' did not exist in the system, this exception is thrown
}Below is an example of using the SDK for Statsig. Note that in Statsig, "feature flags" are called "feature gates":
Uncached gate evaluations use a five-second HTTP timeout by default. Set gateTimeout to a positive number of seconds (including fractions such as 0.5) in the SDK config to use a different limit for a consumer. Other Statsig API requests are not affected.
<?php
use \Carsdotcom\FeatureFlags\Service\Factory\FeatureFlagFactory;
try {
// API keys are set up per environment. Be sure to use the correct apiKey/environment combo.
$sdkConfig = [
'apiKey' => 'API_KEY',
'environment' => 'production', // 'development', 'staging', or 'production'
'gateTimeout' => 1,
'cache' => [
'scheme' => 'tcp', // 'tcp' or 'tls'
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'prefix' => 'flags',
]
];
// CCID is used for user targeting and segments.
$userId = '123';
// Use the FeatureFlagFactory for instantiation- not the StatsigFeatureFlag class directly.
// If we migrate feature flag providers again, we only have to change the factory.
$flags = FeatureFlagFactory::create($sdkConfig, $userId);
// Check if a feature flag (gate) is enabled for the current user
if ($flags->enabled('my-new-feature')) {
// Calling $flags->exists() before $flags->enabled() is not needed due to our Statsig implementation.
// Doing so adds unnecessary overhead.
// Calling $flags->enabled() on a non-existent flag returns false.
}
// Check if a feature flag (gate) name exists
if ($flags->exists('my-new-feature')) {
// ...
}
// Get all available feature flags (gates) names
$allFlags = $flags->all();
// Change the user (uncommon use case)
// Again, note the use of FeatureFlagFactory instead of the StatsigFeatureFlagUser class.
$flags->setUser(FeatureFlagFactory::createUser('some-other-CCID'));
} catch (\Carsdotcom\FeatureFlags\Exceptions\InvalidFeatureFlagUserException $exception) {
// the user passed was invalid. This could happen if the user identifier wasn't sent during creation
} catch (\Carsdotcom\FeatureFlags\Exceptions\InvalidFeatureFlagException $exception) {
// if the feature flag we called '$flags->enabled()' did not exist in the system, this exception is thrown
}enabled() returns false both when a flag is off and when the provider could not be asked (for Statsig: a timeout, a network error, a non-200 response, or a body without a boolean value). That is the right answer for hiding a feature, but not for code that deletes data when a flag is off. Such callers use getFlagState(), which returns one of three FlagState constants:
FlagState::ON: the flag is on for this user.FlagState::OFF: the provider answered that the flag is off, or the flag does not exist.FlagState::UNAVAILABLE: the flag could not be evaluated; do not treat it as off.
Caching:
- An answer from Statsig (on or off) is cached for 5 minutes, and a failed check for 1 minute, so the next call after that asks Statsig again. This applies to
enabled()andgetFlagState(). getFlagState()results are cached under their own keys (flag_state::<flag>::<user>). Theenabled()keys keep holding only booleans, so consumers on older versions of this library sharing the same cache are not affected.enabled()andgetFlagState()do not share cache entries, so calling both for the same flag and user can make two Statsig requests in the same window.- If the cache cannot be read or written,
getFlagState()asks Statsig directly instead of failing, so with both Redis and Statsig down every call waits for the gate timeout (5 seconds unlessgateTimeoutis set). Callers that cannot tolerate that latency should useenabled().
This service will always return as if there are no feature flags enabled/exist and never throw any exceptions.
use \Carsdotcom\FeatureFlags\Service\Null\NullFeatureFlag;
use \Carsdotcom\FeatureFlags\Service\Null\NullFeatureFlagUser;
$flags = new NullFeatureFlag();
// any value can be sent to the NullFeatureFlagUser to instantiate it
$flags->setUser(new NullFeatureFlagUser(null));
// will always return an empty array
$flags->all();
// will always return false
$flags->exists('foobar');
// will always return false
$flags->enabled('foobar');
// will always return FlagState::UNAVAILABLE, since no flag can be evaluated without a provider
$flags->getFlagState('foobar');
// will always return en empty array
$flags->config('foobar');