Skip to content

feat: Implement Multiprovider feature#159

Open
Tmakinde wants to merge 22 commits into
open-feature:mainfrom
Tmakinde:feat/multiprovider
Open

feat: Implement Multiprovider feature#159
Tmakinde wants to merge 22 commits into
open-feature:mainfrom
Tmakinde:feat/multiprovider

Conversation

@Tmakinde
Copy link
Copy Markdown
Contributor

@Tmakinde Tmakinde commented Sep 30, 2025

Summary

Adds a new feature that lets you plug in more than one feature flag provider at the same time. Instead of picking one provider up front, you can give the SDK a list, and it will try them in a defined order (or pattern) and return the first good answer. If none succeed, you get a single combined error.

Why this is useful

Real apps often use multiple sources for flags (primary remote service, fallback local file, experimentation provider, etc.). This feature avoids hand-written chaining logic and gives a simple, uniform way to try multiple providers.

How it works (plain flow)

  1. You create a Multiprovider and pass an array of providers. You may optionally name each provider. If you do not name one, the code gives it a unique name automatically.
  2. You call resolveXValue(...) (boolean, string, int, float, object) on the multiprovider.
  3. A strategy decides which providers to try and when to stop.
  4. Each provider is asked for the flag value (one at a time for the default strategy).
  5. If one returns a successful value, that result is returned immediately.
  6. If all fail (throw or error), a single error result is returned summarizing the failure.

Main classes in simple terms

  • Multiprovider: The coordinator. Holds all the providers, calls them in order, chooses a strategy, and returns the final answer.
  • BaseEvaluationStrategy: A template for “how to walk through the list.” It defines the decision points (should we try this provider? should we move to the next? how do we pick the final result?).
  • FirstMatchStrategy (default): Tries providers in order, returns the first success, stops early.
  • FirstSuccessfulStrategy: Alternative strategy with slightly different stop rules (still about first success).
  • ProviderResolutionResult: A small wrapper for one provider’s attempt (either a value or an error).
  • FinalResult: The final outcome after all attempts (either a single good value or a bundle of errors).
  • StrategyEvaluationContext / StrategyPerProviderContext: Helper objects carrying flag info and provider-specific context so strategies can make simple yes/no decisions.

Backward compatibility

  • Existing code using a single provider is unchanged.
  • No existing public APIs were removed or altered.
  • The new classes are additive and live in their own folder.
  • If you do nothing, the SDK works exactly as before.

Testing

What was added:

  • Tests for the multiprovider basic resolution path.
  • Tests for strategies (ensuring the first matching provider wins; error path when all fail).
  • Tests for final result object (success vs aggregated errors).
  • Tests for validation (rejects duplicate explicit names, rejects empty name strings).

Simpler example

$multi = new Multiprovider([
    ['provider' => $primary],
    ['name' => 'audit', 'provider' => $audit],
    ['provider' => $fallback],
]);

$result = $multi->resolveBooleanValue('beta-feature', false);

$value = $result->getValue(); // either a resolved boolean or the default
$error = $result->getError(); // null if success

Follow-up improvements (non-blocking)

  • README section explaining multi-provider setup.

@Tmakinde Tmakinde requested a review from tcarrio as a code owner September 30, 2025 13:41
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @Tmakinde, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes the foundational Multiprovider class, which is crucial for handling multiple feature flag providers. It focuses on robust input validation and systematic registration of providers, ensuring data integrity and preventing naming conflicts, thereby setting the stage for advanced multiprovider capabilities.

Highlights

  • New Multiprovider Class: Introduced a new Multiprovider class to manage multiple feature flag providers, aligning with the OpenFeature multiprovider specification.
  • Provider Data Validation: Implemented validateProviderData to ensure provider input arrays contain only supported keys, have non-empty names, and prevent duplicate explicit provider names.
  • Provider Registration and Unique Naming: Developed registerProviders and uniqueProviderName methods to register providers by name, automatically generating unique names for providers that are unnamed or have conflicting names by appending a numerical suffix.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Multiprovider class to handle multiple feature flag providers, which is a great step towards implementing the multi-provider feature from the OpenFeature specification. The implementation correctly handles validation of provider data structure and generation of unique names for unnamed providers. However, I've found a high-severity issue in the provider registration logic that could lead to providers being silently overwritten if there's a name collision between an explicitly named provider and a previously registered provider with a generated name. My review includes a suggested fix to prevent this and ensure all provider names are unique as required by the specification.

Comment thread src/implementation/multiprovider/Multiprovider.php Outdated
@tcarrio
Copy link
Copy Markdown
Member

tcarrio commented Sep 30, 2025

Hey @Tmakinde , initial thoughts here is that other languages utilized the contrib repository for implementing the multi provider. The reasoning as I understand it is it's an extension of the primary provider, not a function of it itself.

That said it would be fairly straightforward to add support for it there

@beeme1mr is that the expectation for all languages?

@Tmakinde
Copy link
Copy Markdown
Contributor Author

Hey @Tmakinde , initial thoughts here is that other languages utilized the contrib repository for implementing the multi provider. The reasoning as I understand it is it's an extension of the primary provider, not a function of it itself.

That said it would be fairly straightforward to add support for it there

@beeme1mr is that the expectation for all languages?

Ooh.

Please let me know the best place to implement this.
@beeme1mr told me it should be in php-sdk repo.

Please confirm.

@beeme1mr
Copy link
Copy Markdown
Member

We've been slowly moving the multi-providers to the SDKs themselves. The logic being that it's easier to access and manage provider state from within the SDK and it shouldn't add too much complexity to the SDKs. The .NET SDK is a good example of what we're looking for.

@Tmakinde
Copy link
Copy Markdown
Contributor Author

We've been slowly moving the multi-providers to the SDKs themselves. The logic being that it's easier to access and manage provider state from within the SDK and it shouldn't add too much complexity to the SDKs. The .NET SDK is a good example of what we're looking for.

Thank you @beeme1mr

@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 22, 2025

Codecov Report

❌ Patch coverage is 96.35036% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.57%. Comparing base (2123274) to head (86fffbf).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
...tion/multiprovider/strategy/ComparisonStrategy.php 91.30% 4 Missing ⚠️
src/implementation/multiprovider/MultiProvider.php 97.52% 3 Missing ⚠️
.../multiprovider/strategy/BaseEvaluationStrategy.php 84.61% 2 Missing ⚠️
...tion/multiprovider/strategy/FirstMatchStrategy.php 97.29% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #159      +/-   ##
============================================
+ Coverage     95.19%   96.57%   +1.37%     
- Complexity      227      335     +108     
============================================
  Files            40       49       +9     
  Lines           583      847     +264     
============================================
+ Hits            555      818     +263     
- Misses           28       29       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Tmakinde Tmakinde changed the title [WIP] feat: Created methods to validate and register providers input feat: Implement Multiprovider feature Nov 17, 2025
@Tmakinde Tmakinde force-pushed the feat/multiprovider branch 2 times, most recently from dd766e9 to 418f73a Compare November 17, 2025 09:05
Copy link
Copy Markdown
Member

@tcarrio tcarrio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some suggestions for the current PR state.

I am on vacation so can't provide a deeper review for some time.

Comment thread src/implementation/multiprovider/Multiprovider.php Outdated
Comment thread src/implementation/multiprovider/strategy/BaseEvaluationStrategy.php Outdated
Comment thread src/implementation/multiprovider/strategy/ComparisonStrategy.php Outdated
Comment thread src/implementation/multiprovider/strategy/StrategyEvaluationContext.php Outdated
@beeme1mr beeme1mr linked an issue Dec 23, 2025 that may be closed by this pull request
@Tmakinde Tmakinde force-pushed the feat/multiprovider branch 2 times, most recently from 21096b0 to bfd4ac6 Compare January 5, 2026 07:11
Comment thread src/implementation/multiprovider/Multiprovider.php Outdated
Comment thread src/implementation/multiprovider/Multiprovider.php Outdated
Comment thread src/implementation/multiprovider/Multiprovider.php Outdated
@tcarrio
Copy link
Copy Markdown
Member

tcarrio commented Apr 30, 2026

@Tmakinde Beyond the suggestions in the PR thus far, would you be interested in being included as a code owner for the multiprovider namespace?

If so, feel free to update the code owners as part of this pull request.

Copy link
Copy Markdown
Member

@tcarrio tcarrio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No serious blockers, but some small updates you can make before we wrap. Now approving as the current implementation looks good to me.

Comment thread src/implementation/multiprovider/strategy/BaseEvaluationStrategy.php Outdated
Comment thread src/implementation/multiprovider/MultiProvider.php Outdated
Comment thread src/implementation/multiprovider/MultiProvider.php Outdated
Comment thread tests/unit/MultiProviderStrategyTest.php Outdated
Comment thread src/implementation/multiprovider/MultiProvider.php Outdated
Comment thread src/implementation/multiprovider/strategy/ComparisonStrategy.php
Comment thread src/implementation/multiprovider/MultiProvider.php
Comment thread src/implementation/multiprovider/MultiProvider.php Outdated
Comment thread src/implementation/multiprovider/ProviderResolutionResult.php
Comment thread src/implementation/multiprovider/MultiProvider.php Outdated
Comment thread tests/unit/MultiproviderTest.php Outdated
Comment thread src/implementation/multiprovider/MultiProvider.php
@Tmakinde Tmakinde force-pushed the feat/multiprovider branch from 2a243c7 to 8e23c5d Compare May 22, 2026 20:12
Comment thread src/implementation/multiprovider/strategy/ComparisonStrategy.php Outdated
Comment thread README.md Outdated
Copy link
Copy Markdown
Member

@jonathannorris jonathannorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the last two nits, but looks good! Thanks for implementing this!

@jonathannorris jonathannorris requested a review from toddbaert May 26, 2026 17:27
Tmakinde added 22 commits May 30, 2026 18:57
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
…tated in documentation

Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
…e context

Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
- Rename StrategyEvaluationContext → StrategyContext for clarity
- Rename StrategyPerProviderContext → ProviderContext for conciseness
- Remove redundant type validation from StrategyContext constructor
  (validation now happens via assertions in evaluateProvider)
- Simplify type resolution using match expression instead of switch
- Add type annotation to $defaultValue for Psalm static analysis
- Simplify provider validation with single-pass loop and better error messages
- Streamline provider registration logic (removed redundant duplicate check)
- Add aggregateErrors() helper in BaseEvaluationStrategy to DRY error collection
- Rename class from Multiprovider → MultiProvider (including filename)

This refactoring reduces code duplication (~60 lines), improves maintainability,
and makes the codebase more idiomatic with modern PHP patterns.

Signed-off-by: tmakinde <makindet74@gmail.com>
- Rename test files: StrategyEvaluationContextTest → StrategyContextTest
- Rename test files: StrategyPerProviderContextTest → ProviderContextTest
- Update test assertions to match new error messages
  - 'Duplicate provider names found' → 'Duplicate provider name'
  - 'Each provider data entry must have...' → 'Provider name cannot be empty'
  - 'Unsupported keys in provider data entry' → 'Unsupported keys: <list>'
- Update MultiProviderStrategyTest to pass ProviderContext to strategy methods
- Remove validation tests from StrategyContextTest (validation removed from constructor)
- All 197 tests pass with 433 assertions

Signed-off-by: tmakinde <makindet74@gmail.com>
Add @Tmakinde as code owner for the multiprovider implementation.
This ensures appropriate review oversight for future changes to this
feature area.

Signed-off-by: tmakinde <makindet74@gmail.com>
…idation

- Update validateProviderData to use case-insensitive comparison
- Provider names 'ProviderA' and 'providera' now treated as duplicates
- Add test case to verify case-insensitive duplicate detection
- Original case preserved in error messages

Signed-off-by: tmakinde <makindet74@gmail.com>
- Use count() > 0 instead of !== [] in aggregateErrors for explicit intent
- Keep case-sensitive naming in generateUniqueName (ProviderA_2 vs providera_2)
- Add import alias for ResolutionError interface in tests for clarity

Signed-off-by: tmakinde <makindet74@gmail.com>
- Add MultiProvider to features table with implemented status
- Document MultiProvider usage under Providers section
- Explain three evaluation strategies (FirstMatchStrategy, FirstSuccessfulStrategy, ComparisonStrategy)
- Include provider naming conventions (explicit and auto-generated)
- Add strategy comparison table for easy selection
- Document error handling and aggregation behavior
- Provide complete working examples for each use case

Signed-off-by: tmakinde <makindet74@gmail.com>
…prove docs

BREAKING CHANGE: ComparisonStrategy constructor signature changed

- ComparisonStrategy now requires Provider instance as fallback (not optional string name)
- Implements fail-fast error handling - returns immediately if ANY provider errors
- Fallback provider only used for value mismatches (not error recovery)
- Strict equality (===) comparison for all value types

Error Handling:
- Enhanced error aggregation with provider-level details
- Format: "Multi-provider evaluation failed with X provider error(s): [ProviderA]: message"
- Separated aggregation logic into dedicated aggregateErrorMessage() method

Provider Naming:
- Fixed case-sensitivity inconsistency in validation and storage
- All provider names normalized to lowercase for case-insensitive lookup
- Auto-generated names also lowercased

Documentation:
- Restructured README to match JS-SDK clarity and flow
- Added comprehensive Custom Strategies section with working examples
- Documented known limitation: sub-provider hooks not executed
- Updated all examples and use cases
- Added error aggregation examples

Testing:
- Updated ComparisonStrategyTest for new fail-fast behavior
- Added tests for fallback provider requirement
- All 198 tests passing with proper type safety

Renamed RunMode::PARALLEL to RunMode::EVALUATE_ALL for clarity

Signed-off-by: tmakinde <makindet74@gmail.com>
Add edge case tests:

- Test single provider scenario
- Test mismatch without onMismatch callback
- Test getter methods for fallbackProvider and onMismatch
- Test getOnMismatch returns null when not provided

Signed-off-by: tmakinde <makindet74@gmail.com>
Signed-off-by: tmakinde <makindet74@gmail.com>
@tcarrio tcarrio force-pushed the feat/multiprovider branch from bcde78a to 86fffbf Compare May 30, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Implement Mutliprovider

4 participants