Featurevisor

Migrating the PHP SDK for Featurevisor v4

Upgrade PHP applications to SDK 4.0.0 for Featurevisor v4 support.

Follow the shared migration order: prepare canonical prerequisite datafiles, upgrade consumers, then upgrade the project CLI. Before publishing new runtime fields, follow Adopt new syntax safely.

Convert Groups before upgrading

This SDK silently ignores legacy feature ranges, with no compatibility handling or warning. Convert Groups to flat list requirements while still on the previous compatible SDK and v3 CLI, then publish all datafiles, caches and bundled copies before upgrading. Unconverted files can change cohorts or enable features without a variation. Old cohorts are not guaranteed. Follow Groups removed.

Minimal migration path

Most PHP application code does not change. The following APIs were already available in the previous SDK line and keep the same role:

  • Featurevisor::createFeaturevisor() and setDatafile()
  • isEnabled(), getVariation(), and getVariable()
  • evaluateFlag(), evaluateVariation(), and evaluateVariable()
  • stickyFeatures, stickyVariables, setStickyFeatures(), and setStickyVariables()
  • getFeatureEvaluations(), getVariableEvaluations(), and child instances
  • module lifecycle, diagnostics, and the existing split sticky events

PHP did not expose the JavaScript setSticky() alias or getAllEvaluations() helper in the previous SDK line, so there is no PHP replacement to make for those JavaScript removals.

If an application already uses the canonical module callbacks, does not inspect renamed evaluation metadata, and passes raw datafile JSON, its source migration can be limited to updating the package and testing its datafiles.

See the PHP SDK guide for the complete current API.

Breaking changes

Root condition reason Breaking

An unmet root gate reports conditions_unmet for feature flags and global variables, replacing the v3 required reason for feature flags and required_features_unmet for global variables. This applies to attribute, segment, feature and variable leaves in root conditions. Update callbacks, analytics and test expectations. Variation and feature variable evaluations retain their disabled reasons. See the shared migration guidance.

Before
$unmetRequirement = $evaluation['reason'] === 'required';
After
$unmetRequirement = $evaluation['reason'] === 'conditions_unmet';

Datafile update result Breaking

setDatafile now returns true when it accepts the update, including unchanged content, and false when it rejects the update or the instance is closed. A rejected update keeps the previous datafile and reports details through diagnostics. Calls that ignore the result keep working. Update any wrapper or interface that declares the old return type. Merging remains the default.

Before
$f->setDatafile($datafile);
After
$accepted = $f->setDatafile($datafile);
if (!$accepted) {
error_log('Datafile rejected. Check diagnostics.');
}

Module callbacks Breaking

The deprecated feature only before and after module callbacks are removed. beforeEvaluation and afterEvaluation were already available in the previous SDK line, so applications can adopt them before the breaking upgrade.

Before migration
use Featurevisor\Featurevisor;
$f = Featurevisor::createFeaturevisor([
'datafile' => $datafile,
'modules' => [[
'before' => static function (array $options): array {
$options['context']['country'] = 'nl';
return $options;
},
'after' => static function (
array $evaluation,
array $options
): array {
return $evaluation;
},
]],
]);
After migration
use Featurevisor\Featurevisor;
$f = Featurevisor::createFeaturevisor([
'datafile' => $datafile,
'modules' => [[
'beforeEvaluation' => static function (array $options): array {
if (!isset($options['featureKey'])) return $options;
$options['context']['country'] = 'nl';
return $options;
},
'afterEvaluation' => static function (
array $evaluation,
array $options
): array {
return $evaluation;
},
]],
]);

The canonical callbacks cover feature and global variable evaluations, including prerequisite evaluations. The guard above preserves feature only behaviour. Omit it when the callback should also transform global variable evaluations, and add the same guard to afterEvaluation when its logic is feature specific.

If modules currently define both callback families, preserve their intended ordering while consolidating them. On the previous SDK line, the legacy before family runs before beforeEvaluation, while afterEvaluation runs before the legacy after family. After migration, the SDK runs only the canonical callbacks in module registration order. setup, bucketKey, bucketValue, and close are unchanged.

Detailed evaluation fields Breaking

Detailed evaluations are PHP arrays. Rename the two compatibility metadata fields when application code reads them:

Before migration
$evaluation = $f->evaluateFlag('checkout', $context);
$requirements = $evaluation['required'] ?? null;
$stickyFeature = $evaluation['sticky'] ?? null;
After migration
$evaluation = $f->evaluateFlag('checkout', $context);
$requirements = $evaluation['conditions'] ?? null;
$stickyFeature = $evaluation['stickyFeature'] ?? null;

Use reason === 'sticky' to identify a sticky result. stickyFeature is optional metadata and is not present for every sticky result.

conditions now holds the failed root gate expression. It covers attribute, segment, feature and variable conditions.

The legacy Evaluation::VARIABLE_OVERRIDE constant is removed. Existing singular override fields still describe the final matching override. An accumulated global variable evaluation can additionally expose variableOverridePaths, which lists every applied authored path in execution order. A failed variable prerequisite exposes conditions and the reason conditions_unmet.

Number getter Breaking

Variables that hold numbers have the type number, so the typed getter is now getVariableNumber(). It reads number variables and the double variables of datafiles built before v4. getVariableDouble() was removed. Rename calls to the number getter, which returns the same value.

Before
$threshold = $f->getVariableDouble('freeShippingThreshold', $context);
After
$threshold = $f->getVariableNumber('freeShippingThreshold', $context);

Language differences

Preserve JSON object and array shapes Important

Pass generated datafiles to the compatible runtime as raw JSON whenever possible. PHP arrays cannot represent every JSON container distinction after associative decoding.

Ambiguous after decoding
$json = file_get_contents($datafileUrl);
$datafile = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$f = Featurevisor::createFeaturevisor([
'datafile' => $datafile,
]);
Preserve the JSON shapes
$datafile = file_get_contents($datafileUrl);
$f = Featurevisor::createFeaturevisor([
'datafile' => $datafile,
]);

Raw JSON preserves {} separately from [], and preserves an object such as {"0": "zero"} separately from an array. This matters for compiled mutation paths and variable condition paths because object string keys and array indexes have different behaviour. The same recommendation applies to later setDatafile() calls.

Decoded PHP arrays remain accepted. Non list associative arrays are treated as objects and lists are treated as arrays. For ambiguous native values, pass an explicit stdClass, such as (object) [], when the value must be an object. Detailed evaluations retain preserved JSON objects as stdClass. Getters and the OpenFeature provider adapt them recursively to PHP arrays for compatibility.

OpenFeature

Featurevisor\OpenFeatureProvider remains in the main featurevisor/featurevisor-php Composer package. There is no separate Featurevisor provider package to migrate. The current Composer manifest suggests open-feature/sdk for provider users and uses ^2.3 for development tests. It is not a required dependency for core SDK users.

Install a compatible open-feature/sdk release when using the provider, and update Featurevisor to 4.0.0. If the provider receives an existing $f instance, that instance must come from the same updated Featurevisor package. The OpenFeature constraint does not identify the Featurevisor release version.

The provider constructor and flag key mapping remain unchanged. The updated provider maps conditions_unmet to OpenFeature's disabled reason. Keeping the provider and runtime from the same package also preserves datafile update notifications and JSON value adaptation.

The provider distinguishes a received empty targeting key from an absent one: empty replaces the configured userId or targeting field, while omission preserves it. PHP's upstream client can lose an empty key while merging contexts. Set the configured field itself to "" in attributes if needed, after clearing any inherited nonempty targeting key at its source.

See PHP OpenFeature usage and the shared OpenFeature provider guide.

Migration checklist

Required for the migration

  • Prepare and deploy canonical conditions datafiles before changing the runtime.
  • Replace module before and after with beforeEvaluation and afterEvaluation.
  • Rename detailed evaluation fields used by the application.
  • Rename getVariableDouble() calls to getVariableNumber().
  • Pass datafiles as raw JSON where object and array identity matters.
  • Update the Featurevisor package used by OpenFeature when applicable.
  • Test feature, variation, feature variable, global variable, sticky, child, and module behaviour.
  • Upgrade the project CLI last, review mutation order and date and regex comparisons, and keep a compatible rollback datafile.

Only if adopting optional features

Follow Adopt new syntax safely before publishing new datafile syntax. Upgrade every CLI runner before adding checks or scenarios.