Skip to content

Commit f75549f

Browse files
committed
feat(php): Enable FFE tests and add /ffe endpoints
- Add /ffe/start and /ffe/evaluate to PHP parametric server - Add /ffe.php for end-to-end tests - Remove missing_feature for PHP FFE tests in manifest
1 parent 284ef78 commit f75549f

4 files changed

Lines changed: 100 additions & 4 deletions

File tree

manifests/php.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,6 @@ manifest:
474474
tests/docker_ssi/test_docker_ssi.py::TestDockerSSIFeatures::test_instrumentation_source_ssi:
475475
- declaration: missing_feature (Not implemented yet)
476476
component_version: <1.12.0
477-
tests/ffe/test_dynamic_evaluation.py: missing_feature
478-
tests/ffe/test_exposures.py: missing_feature
479477
tests/integrations/crossed_integrations/test_kafka.py::Test_Kafka: missing_feature
480478
tests/integrations/crossed_integrations/test_kinesis.py::Test_Kinesis_PROPAGATION_VIA_MESSAGE_ATTRIBUTES: missing_feature
481479
tests/integrations/crossed_integrations/test_rabbitmq.py::Test_RabbitMQ_Trace_Context_Propagation: missing_feature
@@ -532,7 +530,6 @@ manifest:
532530
tests/parametric/test_dynamic_configuration.py::TestDynamicConfigV1_EmptyServiceTargets: v1.4.0
533531
tests/parametric/test_dynamic_configuration.py::TestDynamicConfigV1_ServiceTargets: missing_feature
534532
tests/parametric/test_dynamic_configuration.py::TestDynamicConfigV2: missing_feature
535-
tests/parametric/test_ffe/test_dynamic_evaluation.py::Test_Feature_Flag_Dynamic_Evaluation: missing_feature
536533
tests/parametric/test_headers_b3.py::Test_Headers_B3::test_headers_b3_migrated_extract_invalid: missing_feature (Need to remove b3=b3multi alias)
537534
tests/parametric/test_headers_b3.py::Test_Headers_B3::test_headers_b3_migrated_extract_valid: missing_feature (Need to remove b3=b3multi alias)
538535
tests/parametric/test_headers_b3.py::Test_Headers_B3::test_headers_b3_migrated_inject_valid: missing_feature (Need to remove b3=b3multi alias)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
header('Content-Type: application/json');
4+
5+
$input = json_decode(file_get_contents('php://input'), true);
6+
7+
if (!is_array($input)) {
8+
http_response_code(400);
9+
echo json_encode(['error' => 'Invalid JSON body']);
10+
exit;
11+
}
12+
13+
$flag = isset($input['flag']) ? $input['flag'] : null;
14+
$variationType = isset($input['variationType']) ? $input['variationType'] : null;
15+
$defaultValue = isset($input['defaultValue']) ? $input['defaultValue'] : null;
16+
$targetingKey = array_key_exists('targetingKey', $input) ? $input['targetingKey'] : '';
17+
$attributes = isset($input['attributes']) ? $input['attributes'] : [];
18+
19+
try {
20+
// Use OpenFeature API if available, fall back to direct provider
21+
if (class_exists('\OpenFeature\API')) {
22+
$provider = new \DDTrace\OpenFeature\DataDogProvider();
23+
\OpenFeature\API::setProvider($provider);
24+
$client = \OpenFeature\API::getClient();
25+
26+
$context = new \OpenFeature\implementation\flags\EvaluationContext(
27+
$targetingKey,
28+
new \OpenFeature\implementation\flags\Attributes($attributes)
29+
);
30+
31+
$value = match ($variationType) {
32+
'BOOLEAN' => $client->getBooleanValue($flag, (bool) $defaultValue, $context),
33+
'STRING' => $client->getStringValue($flag, (string) $defaultValue, $context),
34+
'INTEGER' => $client->getIntegerValue($flag, (int) $defaultValue, $context),
35+
'NUMERIC' => $client->getFloatValue($flag, (float) $defaultValue, $context),
36+
'JSON' => $client->getObjectValue($flag, is_array($defaultValue) ? $defaultValue : [], $context),
37+
default => $defaultValue,
38+
};
39+
} else {
40+
// Fallback to direct provider (no OpenFeature SDK installed)
41+
$provider = \DDTrace\FeatureFlags\Provider::getInstance();
42+
$provider->start();
43+
$result = $provider->evaluate($flag, $variationType, $defaultValue, $targetingKey, $attributes);
44+
$value = $result['value'];
45+
}
46+
47+
// Flush exposure events immediately for system test observability
48+
\DDTrace\FeatureFlags\Provider::getInstance()->flush();
49+
50+
echo json_encode(['value' => $value]);
51+
} catch (\Throwable $e) {
52+
echo json_encode(['value' => $defaultValue, 'error' => $e->getMessage()]);
53+
}

utils/build/docker/php/parametric/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"amphp/log": "2.x-dev",
88
"open-telemetry/sdk": "^1.0.0",
99
"symfony/http-client": "6.4.x-dev",
10-
"nyholm/psr7": "^1.8@dev"
10+
"nyholm/psr7": "^1.8@dev",
11+
"open-feature/sdk": "^2.0"
1112
},
1213
"config": {
1314
"allow-plugins": {

utils/build/docker/php/parametric/server.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,51 @@ function remappedSpanKind($spanKind) {
546546
return jsonResponse([]);
547547
}));
548548

549+
// FFE (Feature Flags & Experimentation) endpoints
550+
$openFeatureClient = null;
551+
552+
$router->addRoute('POST', '/ffe/start', new ClosureRequestHandler(function (Request $req) use (&$openFeatureClient) {
553+
try {
554+
$provider = new \DDTrace\OpenFeature\DataDogProvider();
555+
\OpenFeature\API::setProvider($provider);
556+
$openFeatureClient = \OpenFeature\API::getClient();
557+
return jsonResponse([]);
558+
} catch (\Throwable $e) {
559+
return new Response(status: 500, body: json_encode(['error' => $e->getMessage()]));
560+
}
561+
}));
562+
563+
$router->addRoute('POST', '/ffe/evaluate', new ClosureRequestHandler(function (Request $req) use (&$openFeatureClient) {
564+
try {
565+
if ($openFeatureClient === null) {
566+
$provider = new \DDTrace\OpenFeature\DataDogProvider();
567+
\OpenFeature\API::setProvider($provider);
568+
$openFeatureClient = \OpenFeature\API::getClient();
569+
}
570+
571+
$flag = arg($req, 'flag');
572+
$variationType = arg($req, 'variationType');
573+
$defaultValue = arg($req, 'defaultValue');
574+
$targetingKey = arg($req, 'targetingKey');
575+
$attributes = arg($req, 'attributes') ?? [];
576+
577+
$context = new \OpenFeature\implementation\flags\EvaluationContext($targetingKey, new \OpenFeature\implementation\flags\Attributes($attributes));
578+
579+
$value = match ($variationType) {
580+
'BOOLEAN' => $openFeatureClient->getBooleanValue($flag, (bool) $defaultValue, $context),
581+
'STRING' => $openFeatureClient->getStringValue($flag, (string) $defaultValue, $context),
582+
'INTEGER' => $openFeatureClient->getIntegerValue($flag, (int) $defaultValue, $context),
583+
'NUMERIC' => $openFeatureClient->getFloatValue($flag, (float) $defaultValue, $context),
584+
'JSON' => $openFeatureClient->getObjectValue($flag, is_array($defaultValue) ? $defaultValue : [], $context),
585+
default => $defaultValue,
586+
};
587+
588+
return jsonResponse(['value' => $value]);
589+
} catch (\Throwable $e) {
590+
return new Response(status: 500, body: json_encode(['error' => $e->getMessage()]));
591+
}
592+
}));
593+
549594
$middleware = new class implements Middleware {
550595
public function handleRequest(Request $request, RequestHandler $next): Response {
551596
$response = $next->handleRequest($request);

0 commit comments

Comments
 (0)