Skip to content

feat(ffe): add runtime-backed PHP feature flag evaluation#3906

Open
leoromanovsky wants to merge 12 commits into
masterfrom
leo.romanovsky/milestone-1-runtime-evaluation
Open

feat(ffe): add runtime-backed PHP feature flag evaluation#3906
leoromanovsky wants to merge 12 commits into
masterfrom
leo.romanovsky/milestone-1-runtime-evaluation

Conversation

@leoromanovsky
Copy link
Copy Markdown

@leoromanovsky leoromanovsky commented May 22, 2026

Motivation

PHP FFE needs a mergeable Milestone 1 artifact that proves real evaluations through Remote Config and the libdatadog Rust evaluator before adding exposure delivery, exposure caching, or flag-evaluation metrics.

This is the evaluation-only product slice: PHP 7 gets a Datadog API, PHP 8 can use the optional OpenFeature bridge, and both read from the live Remote Config-backed native runtime. The remaining product work is intentionally left for later milestones.

Shared planning doc: https://docs.google.com/document/d/1NvMfTpZWLBlFmEFNjdnlMyeVpy5l7KD8qujGFco6w2w/edit?tab=t.0

Changes

  • Imports the libdatadog FFE evaluator path and keeps PHP user-facing APIs as thin adapters over the native runtime.
  • Wires PHP 7-safe DDTrace\FeatureFlags evaluation through live Remote Config.
  • Adds a PHP 8-only DDTrace\OpenFeature optional bridge without adding open-feature/sdk as a root dependency.
  • Adds the canonical DataDog/ffe-system-test-data submodule and a native-runtime PHPT loop over ufc-config.json and evaluation-cases/*.json.

Not included

  • Exposure writer, exposure cache, or EVP forwarding.
  • Exposure flush/dedup tests.
  • Flag-evaluation metric emission.

Decisions

  • The root package remains PHP 7 installable; OpenFeature SDK is installed only in PHP 8 test/app environments.
  • Production clients/providers get data only from the live Remote Config-backed native runtime.
  • DDTrace\Testing\ffe_load_config exists only for local/canonical fixture tests.
  • The canonical fixture PHPT runs in normal extension CI but skips under valgrind (USE_ZEND_ALLOC=0) because it is a semantic fixture sweep over the Rust evaluator, not a memory-leak test.
  • Empty targeting key preservation is a separate C bridge contract fix, not part of Bob's ZTS review point.

Local validation

  • php vendor/bin/phpunit --config phpunit.xml tests/api/Unit/FeatureFlags tests/OpenFeature/DataDogProviderTest.php: 29 tests / 87 assertions.
  • make test_featureflags: 8 tests / 29 assertions.
  • MAX_TEST_PARALLELISM=1 TESTS=tests/ext/ffe/native_bridge_evaluate.phpt make test_c: 1/1 passed.
  • MAX_TEST_PARALLELISM=1 TESTS=tests/ext/ffe/system_test_data_evaluate.phpt make test_c: 1/1 passed.
  • make test_internal_api_randomized: passed.

Dogfooding validation

See DataDog/ffe-dogfooding#68.

The local compose stack demonstrated PHP 7 and PHP 8/OpenFeature receiving live Remote Config updates and returning non-default values matching GA SDKs, including JSON/object variants.

Screenshot 2026-05-22 at 12 57 05 PM

System-tests validation

See DataDog/system-tests#7003.

Built a local PHP 8.2 arm64 artifact with:

./tooling/bin/build-debug-artifact gnu-aarch64-8.2-nts /Users/leo.romanovsky/go/src/github.com/DataDog/system-tests-pr-g-php-ffe-scaffold/binaries

Validated against PHP parametric system-tests:

  • focused test-json-config-flag.json: 1/1 passed.
  • focused test-case-of-7-empty-targeting-key.json: 1/1 passed.
  • full Test_Feature_Flag_Dynamic_Evaluation: 25/25 passed.
  • Test_Parametric_FFE_Start: 1/1 passed.

@datadog-datadog-prod-us1-2
Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 60.70% (-0.03%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 782f622 | Docs | Datadog PR Page | Give us feedback!

@datadog-datadog-prod-us1-2
Copy link
Copy Markdown

datadog-datadog-prod-us1-2 Bot commented May 22, 2026

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 60.70% (-0.05%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 555e9a0 | Docs | Datadog PR Page | Give us feedback!

@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented May 22, 2026

Benchmarks [ tracer ]

Benchmark execution time: 2026-05-22 20:45:03

Comparing candidate commit 555e9a0 in PR branch leo.romanovsky/milestone-1-runtime-evaluation with baseline commit 0fb4c56 in branch master.

Found 0 performance improvements and 4 performance regressions! Performance is the same for 190 metrics, 0 unstable metrics.

scenario:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟥 execution_time [+3.738µs; +5.742µs] or [+3.692%; +5.672%]

scenario:PDOBench/benchPDOOverhead

  • 🟥 execution_time [+6.239µs; +8.135µs] or [+2.534%; +3.304%]

scenario:PDOBench/benchPDOOverheadWithDBM

  • 🟥 execution_time [+7.317µs; +9.664µs] or [+2.986%; +3.944%]

scenario:PHPRedisBench/benchRedisOverhead

  • 🟥 execution_time [+29.168µs; +41.884µs] or [+3.025%; +4.344%]

@leoromanovsky leoromanovsky marked this pull request as ready for review May 22, 2026 18:02
@leoromanovsky leoromanovsky requested review from a team as code owners May 22, 2026 18:02
@leoromanovsky leoromanovsky requested review from tabgok and removed request for a team May 22, 2026 18:02
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e55ebb976b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/bridge/_files_openfeature.php Outdated
Comment thread components-rs/ffe.rs
Comment on lines +302 to +303
EvaluationError::FlagDisabled => (ERROR_NONE, REASON_DISABLED),
EvaluationError::DefaultAllocationNull => (ERROR_NONE, REASON_DEFAULT),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve disabled/default results instead of null coercion

For a disabled flag this returns error_code = 0 with value_json = "null", but ResultMapper::map() treats any zero-error result as a real variation value and then fails to coerce null to the requested type, converting disabled flags into TYPE_MISMATCH errors instead of returning the caller default with EvaluationReason::DISABLED. The same path affects DefaultAllocationNull; handle these no-assignment cases before decoding or surface an error/default signal that the PHP mapper can distinguish.

Useful? React with 👍 / 👎.

@leoromanovsky leoromanovsky marked this pull request as draft May 22, 2026 18:57
@leoromanovsky leoromanovsky marked this pull request as ready for review May 22, 2026 19:04
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.

4 participants