diff --git a/docs/reference/sdks/client/kotlin.mdx b/docs/reference/sdks/client/kotlin.mdx index cdd3bd09c..9f87242fe 100644 --- a/docs/reference/sdks/client/kotlin.mdx +++ b/docs/reference/sdks/client/kotlin.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from kotlin-sdk. Edits should be made here: https://github.com/open-feature/kotlin-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:34 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/client/swift.mdx b/docs/reference/sdks/client/swift.mdx index a975bf295..9b66190a5 100644 --- a/docs/reference/sdks/client/swift.mdx +++ b/docs/reference/sdks/client/swift.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from swift-sdk. Edits should be made here: https://github.com/open-feature/swift-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:35 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/client/web/angular.mdx b/docs/reference/sdks/client/web/angular.mdx index 9b0183c31..b2ca4b323 100644 --- a/docs/reference/sdks/client/web/angular.mdx +++ b/docs/reference/sdks/client/web/angular.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from js-sdk. Edits should be made here: https://github.com/open-feature/js-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:35 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) -->

@@ -18,8 +18,8 @@ Last updated at Mon Jun 08 2026 09:46:35 GMT+0000 (Coordinated Universal Time) Specification - - Release + + Release
@@ -46,8 +46,8 @@ In addition to the features provided by the [web sdk](/docs/reference/sdks/clien - [yarn](#yarn) - [Required peer dependencies](#required-peer-dependencies) - [Usage](#usage) - - [Module](#module) - - [Minimal Example](#minimal-example) + - [Standalone configuration (recommended)](#standalone-configuration-recommended) + - [NgModule configuration (deprecated)](#ngmodule-configuration-deprecated) - [How to use](#how-to-use) - [Structural Directives](#structural-directives) - [Boolean Feature Flag](#boolean-feature-flag) @@ -100,9 +100,44 @@ See the [package.json](https://github.com/open-feature/js-sdk/blob/main/packages ### Usage -#### Module +#### Standalone configuration (recommended) -To configure OpenFeature for your application, import the `OpenFeatureModule` and call `forRoot` to register the provider(s) and optional evaluation context. +For standalone / `bootstrapApplication`-based apps, pass `provideOpenFeature()` in the `providers` array of your `ApplicationConfig`: + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { provideOpenFeature } from '@openfeature/angular-sdk'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideOpenFeature({ + provider: yourFeatureProvider, + // domainBoundProviders are optional, mostly needed if more than one provider is used in the application. + domainBoundProviders: { + domain1: new YourOpenFeatureProvider(), + domain2: new YourOtherOpenFeatureProvider(), + }, + }), + ], +}; +``` + +Then bootstrap your app with this config: + +```typescript +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { appConfig } from './app/app.config'; + +bootstrapApplication(AppComponent, appConfig); +``` + +#### NgModule configuration (deprecated) + +> [!WARNING] +> `OpenFeatureModule` and `forRoot()` are deprecated. Use [`provideOpenFeature()`](#standalone-configuration-recommended) instead. + +To configure OpenFeature for NgModule-based applications, import the `OpenFeatureModule` and call `forRoot` to register the provider(s) and optional evaluation context. ```typescript import { NgModule } from '@angular/core'; @@ -358,20 +393,19 @@ const flag$ = this.flagService.getBooleanDetails('my-flag', false, 'my-domain', ##### Setting evaluation context -To set the initial evaluation context, you can add the `context` parameter to the `OpenFeatureModule` configuration. +To set the initial evaluation context, you can add the `context` parameter to the `provideOpenFeature()` configuration. This context can be either an object or a factory function that returns an `EvaluationContext`. > [!TIP] > Updating the context can be done directly via the global OpenFeature API using `OpenFeature.setContext()` -Here’s how you can define and use the initial client evaluation context: +Here's how you can define and use the initial client evaluation context: ###### Using a static object ```typescript -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { OpenFeatureModule } from '@openfeature/angular-sdk'; +import { ApplicationConfig } from '@angular/core'; +import { provideOpenFeature } from '@openfeature/angular-sdk'; const initialContext = { user: { @@ -380,37 +414,32 @@ const initialContext = { }, }; -@NgModule({ - imports: [ - CommonModule, - OpenFeatureModule.forRoot({ +export const appConfig: ApplicationConfig = { + providers: [ + provideOpenFeature({ provider: yourFeatureProvider, context: initialContext, }), ], -}) -export class AppModule {} +}; ``` ###### Using a factory function ```typescript -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { OpenFeatureModule, EvaluationContext } from '@openfeature/angular-sdk'; +import { ApplicationConfig } from '@angular/core'; +import { provideOpenFeature, EvaluationContext } from '@openfeature/angular-sdk'; const contextFactory = (): EvaluationContext => loadContextFromLocalStorage(); -@NgModule({ - imports: [ - CommonModule, - OpenFeatureModule.forRoot({ +export const appConfig: ApplicationConfig = { + providers: [ + provideOpenFeature({ provider: yourFeatureProvider, context: contextFactory, }), ], -}) -export class AppModule {} +}; ``` ##### Observability considerations diff --git a/docs/reference/sdks/client/web/index.mdx b/docs/reference/sdks/client/web/index.mdx index be199c58d..10855a24d 100644 --- a/docs/reference/sdks/client/web/index.mdx +++ b/docs/reference/sdks/client/web/index.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from js-sdk. Edits should be made here: https://github.com/open-feature/js-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:34 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/client/web/react.mdx b/docs/reference/sdks/client/web/react.mdx index 32c16bd41..69b2ab734 100644 --- a/docs/reference/sdks/client/web/react.mdx +++ b/docs/reference/sdks/client/web/react.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from js-sdk. Edits should be made here: https://github.com/open-feature/js-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:34 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/cpp.mdx b/docs/reference/sdks/server/cpp.mdx index c15152f02..b4852af0d 100644 --- a/docs/reference/sdks/server/cpp.mdx +++ b/docs/reference/sdks/server/cpp.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from cpp-sdk. Edits should be made here: https://github.com/open-feature/cpp-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:36 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) -->

diff --git a/docs/reference/sdks/server/dart.mdx b/docs/reference/sdks/server/dart.mdx index 22e51ac72..7bc9621fa 100644 --- a/docs/reference/sdks/server/dart.mdx +++ b/docs/reference/sdks/server/dart.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from dart-server-sdk. Edits should be made here: https://github.com/open-feature/dart-server-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:35 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) -->

diff --git a/docs/reference/sdks/server/dotnet.mdx b/docs/reference/sdks/server/dotnet.mdx index acb6e8a18..d7aa275a4 100644 --- a/docs/reference/sdks/server/dotnet.mdx +++ b/docs/reference/sdks/server/dotnet.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from dotnet-sdk. Edits should be made here: https://github.com/open-feature/dotnet-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:33 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/go.mdx b/docs/reference/sdks/server/go.mdx index 1a316a6e8..698cdae00 100644 --- a/docs/reference/sdks/server/go.mdx +++ b/docs/reference/sdks/server/go.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from go-sdk. Edits should be made here: https://github.com/open-feature/go-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:33 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/java.mdx b/docs/reference/sdks/server/java.mdx index 123c2205e..76606cf22 100644 --- a/docs/reference/sdks/server/java.mdx +++ b/docs/reference/sdks/server/java.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from java-sdk. Edits should be made here: https://github.com/open-feature/java-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:33 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:34:59 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/javascript/index.mdx b/docs/reference/sdks/server/javascript/index.mdx index bd6f4d5d7..8548deb9a 100644 --- a/docs/reference/sdks/server/javascript/index.mdx +++ b/docs/reference/sdks/server/javascript/index.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from js-sdk. Edits should be made here: https://github.com/open-feature/js-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:33 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:34:59 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/javascript/nestjs.mdx b/docs/reference/sdks/server/javascript/nestjs.mdx index 218f08638..1372be7cf 100644 --- a/docs/reference/sdks/server/javascript/nestjs.mdx +++ b/docs/reference/sdks/server/javascript/nestjs.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from js-sdk. Edits should be made here: https://github.com/open-feature/js-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:33 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/php.mdx b/docs/reference/sdks/server/php.mdx index 0cc027895..35cb4a1ed 100644 --- a/docs/reference/sdks/server/php.mdx +++ b/docs/reference/sdks/server/php.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from php-sdk. Edits should be made here: https://github.com/open-feature/php-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:34 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; @@ -21,8 +21,8 @@ import MCPInstall from '@site/src/partials/mcp-install'; - - Release + + Release @@ -144,6 +144,7 @@ class MyClass | ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](/docs/reference/concepts/evaluation-context). | | ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | | ✅ | [Logging](#logging) | Integrate with popular logging packages. | +| ✅ | [MultiProvider](#multiprovider) | Combine multiple providers with configurable evaluation strategies for fallback and aggregation. | | ❌ | [Named clients](#named-clients) | Utilize multiple providers in a single application. | | ⚠️ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | | ❌ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | @@ -167,6 +168,358 @@ $api->setProvider(new MyProvider()); +#### MultiProvider + +The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature SDK. When a flag is being evaluated, the Multi-Provider will consult each underlying provider it is managing in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used. + +The Multi-Provider is a powerful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. For example: + +- **Migration**: When migrating between two providers, you can run both in parallel under a unified flagging interface. As flags are added to the new provider, the Multi-Provider will automatically find and return them, falling back to the old provider if the new provider does not have the flag. +- **Multiple Data Sources**: The Multi-Provider allows you to seamlessly combine many sources of flagging data, such as environment variables, local files, database values, and SaaS-hosted feature management systems. +- **High Availability**: Use multiple providers as redundant sources, automatically failing over when one provider is unavailable. + +**Basic Usage** + +The Multi-Provider is initialized with an array of providers it should evaluate: + +```php +use OpenFeature\OpenFeatureAPI; +use OpenFeature\implementation\multiprovider\MultiProvider; + +$multiProvider = new MultiProvider([ + ['provider' => new ProviderA()], + ['provider' => new ProviderB()], +]); + +$api = OpenFeatureAPI::getInstance(); +$api->setProvider($multiProvider); + +$client = $api->getClient(); +echo $client->getBooleanValue('my-flag', false); +``` + +By default, the Multi-Provider will evaluate all underlying providers in order and return the **first successful result**. If a provider indicates it does not have a flag (`FLAG_NOT_FOUND` error code), then it will be skipped and the next provider will be evaluated. If any provider throws or returns an error result, the operation will fail and the error will be returned. If no provider returns a successful result, the operation will fail with a `FLAG_NOT_FOUND` error code. + +To change this behaviour, a different "strategy" can be provided: + +```php +use OpenFeature\implementation\multiprovider\strategy\FirstSuccessfulStrategy; + +$multiProvider = new MultiProvider([ + ['provider' => new ProviderA()], + ['provider' => new ProviderB()], +], new FirstSuccessfulStrategy()); +``` + +The Multi-Provider comes with three strategies out of the box: + +1. **FirstMatchStrategy** (default): Evaluates all providers in order and returns the first successful result. Providers that indicate `FLAG_NOT_FOUND` error will be skipped and the next provider will be evaluated. Any other error will cause the operation to fail and the error to be returned. +2. **FirstSuccessfulStrategy**: Evaluates all providers in order and returns the first successful result. Any error will cause that provider to be skipped. If no successful result is returned, the set of errors will be returned. +3. **ComparisonStrategy**: Evaluates all providers at one time. If every provider returns a successful result with the same value, then that result is returned. Otherwise, an error is returned immediately if any provider errors. When values do not agree, an optional callback will be executed to notify you of the mismatch, and the configured "fallback provider" value will be used. This can be useful when migrating between providers that are expected to contain identical configuration. You can easily spot mismatches in configuration without affecting flag behaviour. + +**Provider Naming** + +Providers can be named explicitly or have names auto-generated: + +```php +// Explicit naming (recommended for clarity) +new MultiProvider([ + ['name' => 'LaunchDarkly', 'provider' => $ldProvider], + ['name' => 'FlagSmith', 'provider' => $fsProvider], +]); + +// Auto-generated naming (provider class name is used) +new MultiProvider([ + ['provider' => $ldProvider], // Named "launchdarkly" (lowercase) + ['provider' => $fsProvider], // Named "flagsmith" (lowercase) + ['provider' => $fsProvider], // Named "flagsmith_2" (auto-incremented, lowercase) +]); +``` + +> **Note:** Provider names are **case-insensitive** and stored in lowercase. "MyProvider", "myprovider", and "MYPROVIDER" are all treated as the same provider name. Auto-generated names from provider metadata are also normalized to lowercase. + +**Strategies** + +##### FirstMatchStrategy (Default) + +Evaluates providers sequentially and returns the **first successful result**. Continues to the next provider only if the current one throws a `FLAG_NOT_FOUND` error. + +**Use cases:** +- Primary/fallback provider setup +- Gradual migration between providers +- Provider priority ordering + +```php +use OpenFeature\implementation\multiprovider\strategy\FirstMatchStrategy; + +$multiProvider = new MultiProvider([ + ['name' => 'Remote', 'provider' => new RemoteProvider()], // Try remote first + ['name' => 'Cache', 'provider' => new CacheProvider()], // Fall back to cache + ['name' => 'Default', 'provider' => new DefaultProvider()], // Final fallback +], new FirstMatchStrategy()); +``` + +**Behavior:** +- **Remote** has the flag → returns Remote's value ✅ +- **Remote** throws `FLAG_NOT_FOUND` → continues to **Cache** +- **Remote** throws other error (e.g., network timeout) → stops and returns error ❌ +- All providers throw `FLAG_NOT_FOUND` → returns default value with aggregated errors + +##### FirstSuccessfulStrategy + +Evaluates providers sequentially and returns the **first successful result**, skipping providers that throw **any error** (not just `FLAG_NOT_FOUND`). + +**Use cases:** +- High availability setups +- Tolerating provider failures +- Failover scenarios + +```php +use OpenFeature\implementation\multiprovider\strategy\FirstSuccessfulStrategy; + +$multiProvider = new MultiProvider([ + ['name' => 'Primary', 'provider' => new UnstableProvider()], + ['name' => 'Secondary', 'provider' => new StableProvider()], + ['name' => 'Tertiary', 'provider' => new LocalProvider()], +], new FirstSuccessfulStrategy()); +``` + +**Behavior:** +- Evaluates providers in order until one succeeds +- Ignores **all types of errors** from failing providers +- Returns the first successful result +- If all providers fail → returns default value with aggregated errors + +##### ComparisonStrategy + +Evaluates **all providers** at one time. If every provider returns a successful result with the same value, then that result is returned. Otherwise, an error is returned immediately if any provider errors. When values do not agree, the configured "fallback provider" value will be used. + +This strategy accepts several arguments during initialization: + +```php +use OpenFeature\implementation\multiprovider\strategy\ComparisonStrategy; + +// Set up providers +$providerA = new ProviderA(); +$multiProvider = new MultiProvider([ + ['provider' => $providerA], + ['provider' => new ProviderB()], +], new ComparisonStrategy( + $providerA, // First argument: "fallback provider" whose value to use when providers don't agree + function($resolutions) { // Second argument: callback when values don't match + error_log('Mismatch detected: ' . json_encode($resolutions)); + } +)); +``` + +The first argument is the "fallback provider" whose value to use in the event that providers do not agree. It should be the same object reference as one of the providers in the list. The second argument is a callback function that will be executed when a mismatch is detected. The callback will be passed an array containing the details of each provider's resolution, including the flag key, the value returned, and any errors that were thrown. + +**Use cases:** +- A/B testing during provider migrations +- Validating new provider implementations against trusted baselines +- Detecting configuration drift across multiple sources +- Ensuring provider consistency before committing to a new provider + +**Behavior:** +- Evaluates **ALL providers** sequentially +- **Fail-fast on errors:** If ANY provider returns an error, immediately returns all errors (no partial results) +- **Compares values:** Uses strict equality (`===`) to check if all providers returned the same value +- **On agreement:** If all providers succeed and all values match, returns the common value +- **On mismatch:** If all succeed but values don't match, calls optional `onMismatch` callback, then returns the fallback provider's value +- **Throws exception** if fallback provider not found in results + +**Important Notes:** +- The fallback provider parameter is **required** (not optional) +- Fallback provider must be included in the provider list +- This is **not** a "highest value wins" strategy - it checks for exact equality +- Fallback provider is only used for value mismatches, NOT for error recovery +- Useful for ensuring consistency during migrations, not for aggregating different values + +**Strategy Comparison** + +| Scenario | FirstMatchStrategy | FirstSuccessfulStrategy | ComparisonStrategy | +|----------|-------------------|------------------------|-------------------| +| Provider order matters | ✅ Yes (stops at first match) | ✅ Yes (stops at first success) | ❌ No (evaluates all) | +| Handles FLAG_NOT_FOUND | Continues to next | Continues to next | Returns all errors | +| Handles other errors | Stops evaluation | Continues to next | Returns all errors immediately | +| Evaluates all providers | ❌ No | ❌ No | ✅ Yes | +| Best for fallback | ✅ | ✅ | ❌ | +| Best for high availability | ❌ | ✅ | ❌ | +| Best for consistency validation | ❌ | ❌ | ✅ | +| Requires fallback provider | ❌ Optional | ❌ Optional | ✅ Required | + +**Error Handling** + +Error handling varies by strategy: + +**FirstMatchStrategy:** +- Continues evaluation when providers throw `FLAG_NOT_FOUND` +- Stops and returns error if provider throws other errors +- Aggregates `FLAG_NOT_FOUND` errors if all providers fail + +**FirstSuccessfulStrategy:** +- Continues evaluation when providers throw any error +- Returns first successful result, ignoring all previous errors +- Aggregates all errors if all providers fail + +**ComparisonStrategy:** +- **Fail-fast:** Returns all errors immediately if ANY provider errors +- Fallback provider only used for value mismatches (not error recovery) +- All providers are evaluated (no short-circuiting on mismatches) +- On value mismatch: Invokes `onMismatch` callback, then returns fallback provider's value (not an error) + +**Error Aggregation:** + +When all providers fail, MultiProvider aggregates individual provider errors into a single detailed error message: + +```php +// Example: FirstMatchStrategy with all FLAG_NOT_FOUND +$result = $client->getBooleanDetails('non-existent-flag', false); +// Returns: value=false, reason=ERROR, +// error="Multi-provider evaluation failed with 3 provider error(s): [ProviderA]: Flag not found; [ProviderB]: Flag not found; [ProviderC]: Flag not found" + +// Example: ComparisonStrategy with provider errors +$result = $client->getBooleanDetails('some-flag', false); +// Returns: value=false, reason=ERROR, +// error="Multi-provider evaluation failed with 2 provider error(s): [ProviderA]: Connection timeout; [ProviderB]: Invalid configuration" +``` + +This detailed error aggregation helps with debugging by showing exactly which provider failed and why, similar to JavaScript's `AggregateError`. + +**Complete Example** + +```php +use OpenFeature\OpenFeatureAPI; +use OpenFeature\implementation\multiprovider\MultiProvider; +use OpenFeature\implementation\multiprovider\strategy\FirstMatchStrategy; + +// Set up providers +$remoteProvider = new LaunchDarklyProvider($config); +$cacheProvider = new RedisCacheProvider($redis); +$defaultProvider = new InMemoryProvider([ + 'feature-enabled' => true, + 'api-timeout' => 30, +]); + +// Configure MultiProvider with priority order +$multiProvider = new MultiProvider([ + ['name' => 'LaunchDarkly', 'provider' => $remoteProvider], + ['name' => 'Redis', 'provider' => $cacheProvider], + ['name' => 'Defaults', 'provider' => $defaultProvider], +], new FirstMatchStrategy()); + +// Register with OpenFeature +$api = OpenFeatureAPI::getInstance(); +$api->setProvider($multiProvider); + +// Use as normal +$client = $api->getClient(); +$enabled = $client->getBooleanValue('feature-enabled', false); + +// Evaluation flow: +// 1. Try LaunchDarkly → if FLAG_NOT_FOUND, continue +// 2. Try Redis → if FLAG_NOT_FOUND, continue +// 3. Try Defaults → returns value +// 4. If all fail → returns default with aggregated errors +``` + +**Custom Strategies** + +It is also possible to implement your own strategy if the above options do not fit your use case. To do so, create a class which extends `BaseEvaluationStrategy`: + +```php +use OpenFeature\implementation\multiprovider\strategy\BaseEvaluationStrategy; +use OpenFeature\implementation\multiprovider\strategy\ProviderContext; +use OpenFeature\implementation\multiprovider\strategy\StrategyContext; +use OpenFeature\implementation\multiprovider\ProviderResolutionResult; +use OpenFeature\implementation\multiprovider\FinalResult; +use OpenFeature\interfaces\provider\RunMode; + +class CustomStrategy extends BaseEvaluationStrategy +{ + // Set to RunMode::EVALUATE_ALL to evaluate all providers at one time + // or leave as default for sequential evaluation + public string $runMode = RunMode::SEQUENTIAL; + + /** + * Called before each provider is evaluated. + * Return false to skip evaluating this provider. + */ + public function shouldEvaluateThisProvider(ProviderContext $context): bool + { + // Custom logic to determine if this provider should be evaluated + return true; + } + + /** + * Called after a provider is evaluated (sequential mode only). + * Return false to stop evaluating remaining providers. + */ + public function shouldEvaluateNextProvider( + ProviderContext $context, + ProviderResolutionResult $result + ): bool { + // Custom logic to determine if evaluation should continue + return true; + } + + /** + * Called after all providers have been evaluated. + * Determines the final result to return based on all provider results. + */ + public function determineFinalResult( + StrategyContext $context, + array $resolutions + ): FinalResult { + // Custom logic to determine which result to return + // Can analyze all results and choose one, aggregate them, etc. + + // Example: Return first successful result + foreach ($resolutions as $resolution) { + if (!$resolution->hasError()) { + return new FinalResult( + $resolution->getDetails(), + $resolution->getProviderName(), + null + ); + } + } + + // All failed - return errors + return new FinalResult(null, null, $this->aggregateErrors($resolutions)); + } +} +``` + +The `$runMode` property determines whether the list of providers will be evaluated sequentially or at once (using `RunMode::EVALUATE_ALL`). + +The `shouldEvaluateThisProvider()` method is called just before a provider is evaluated by the Multi-Provider. If the function returns false, then the provider will be skipped instead of being evaluated. + +The `shouldEvaluateNextProvider()` method is called after a provider is evaluated in sequential mode. If it returns true, the next provider in the sequence will be called, otherwise no more providers will be evaluated. This method is not called when `$runMode` is `RunMode::EVALUATE_ALL`. + +The `determineFinalResult()` method is called after all providers have been evaluated, or the `shouldEvaluateNextProvider()` method returned false. It is called with an array of results from all the individual providers' evaluations. It returns the final result, or can throw an error if needed. + +#### Known Limitations + +**Sub-Provider Hooks Not Executed:** + +Currently, when using MultiProvider, hooks registered on individual sub-providers via `provider->getHooks()` are **not executed** during flag evaluation. Only hooks registered at the API, Client, and Invocation levels (plus MultiProvider's own hooks) are executed. + +In the JS-SDK reference implementation, each sub-provider's hooks are executed around each provider call via a dedicated `HookExecutor`. The PHP `Provider` interface extends `HooksGetter` (per OpenFeature Requirement 2.10), so the mechanism exists—it just isn't wired up in the current implementation. + +**Workaround:** Register hooks at the API or Client level instead of on individual providers: + +```php +// Instead of this (won't execute): +$providerA->addHooks($myHook); + +// Do this (will execute): +$client = $api->getClient(); +$client->addHooks($myHook); +``` + +This limitation will be addressed in a future release where per-provider hook execution will be implemented to match the JS-SDK behavior. + ### Targeting Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. diff --git a/docs/reference/sdks/server/python.mdx b/docs/reference/sdks/server/python.mdx index a3d4b0078..b37e36118 100644 --- a/docs/reference/sdks/server/python.mdx +++ b/docs/reference/sdks/server/python.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from python-sdk. Edits should be made here: https://github.com/open-feature/python-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:34 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:00 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/ruby.mdx b/docs/reference/sdks/server/ruby.mdx index d089b991f..e187b40f7 100644 --- a/docs/reference/sdks/server/ruby.mdx +++ b/docs/reference/sdks/server/ruby.mdx @@ -10,7 +10,7 @@ This content has been automatically generated from ruby-sdk. Edits should be made here: https://github.com/open-feature/ruby-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:35 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) --> import MCPInstall from '@site/src/partials/mcp-install'; diff --git a/docs/reference/sdks/server/rust.mdx b/docs/reference/sdks/server/rust.mdx index f8ea2130c..81d346e48 100644 --- a/docs/reference/sdks/server/rust.mdx +++ b/docs/reference/sdks/server/rust.mdx @@ -9,7 +9,7 @@ This content has been automatically generated from rust-sdk. Edits should be made here: https://github.com/open-feature/rust-sdk Once a repo has been updated, docs can be generated by running: yarn update:sdk-docs -Last updated at Mon Jun 08 2026 09:46:36 GMT+0000 (Coordinated Universal Time) +Last updated at Fri Jun 12 2026 09:35:01 GMT+0000 (Coordinated Universal Time) -->

diff --git a/src/datasets/sdks/sdk-compatibility.json b/src/datasets/sdks/sdk-compatibility.json index 13926faeb..8f7cc8a93 100644 --- a/src/datasets/sdks/sdk-compatibility.json +++ b/src/datasets/sdks/sdk-compatibility.json @@ -304,8 +304,8 @@ "path": "/docs/reference/sdks/server/php", "category": "Server", "release": { - "href": "https://github.com/open-feature/php-sdk/releases/tag/2.1.2", - "version": "2.1.2", + "href": "https://github.com/open-feature/php-sdk/releases/tag/2.2.0", + "version": "2.2.0", "stable": true }, "spec": {