A reference implementation showing how to integrate LaunchDarkly experiment evaluation data with AWS S3 using Kinesis Firehose in PHP applications.
Note: This is the PHP implementation. For the unified project overview, see the root README.md.
- Uses a
VariationDetailAnalyticsWrapperwrapper class aroundvariationDetail()method calls - Automatically detects when users are in experiments and sends data to Firehose
- Follows the same pattern used in LaunchDarkly Mobile SDK integrations
- Data streams to S3 for integration with analytics platforms
LaunchDarkly SDK → PHP Wrapper → Kinesis Firehose → S3
↓
[Your Analytics Platform]
- AWS Account with permissions to create and manage:
- Kinesis Firehose delivery streams
- S3 buckets
- IAM roles and policies
- LaunchDarkly Account with:
- SDK key
- Feature flags with experiments enabled
- PHP 8.1+ with Composer
Note: For detailed AWS resource setup instructions (automated and manual), see the root README.md. The setup instructions are shared between Python and PHP implementations.
cd hello-php
composer installcp env.example .envEdit .env with your credentials. See the root README.md for required environment variables and AWS authentication options.
php main.phpCopy these files to your application:
src/FirehoseSender.php- AWS Kinesis Firehose integrationsrc/VariationDetailAnalyticsWrapper.php- Wrapper for flag evaluation
Add to your composer.json:
{
"require": {
"launchdarkly/server-sdk": "^6.0",
"aws/aws-sdk-php": "^3.0",
"vlucas/phpdotenv": "^5.0"
}
}Run composer install.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use LaunchDarkly\LDClient;
use LaunchDarkly\LDContext;
use LaunchDarkly\FirehoseSender;
use LaunchDarkly\VariationDetailAnalyticsWrapper;
// Initialize LaunchDarkly client
$ldClient = new LDClient($_ENV['LAUNCHDARKLY_SDK_KEY']);
// Initialize Firehose sender (optional - can be null for graceful degradation)
$firehoseSender = null;
try {
$firehoseSender = new FirehoseSender($_ENV['FIREHOSE_STREAM_NAME']);
} catch (Exception $e) {
// Log error but continue without Firehose
error_log("Failed to initialize Firehose sender: " . $e->getMessage());
}
// Initialize wrapper
$analyticsWrapper = new VariationDetailAnalyticsWrapper($ldClient, $firehoseSender);Before:
$evaluationDetail = $ldClient->variationDetail($flagKey, $context, $defaultValue);
$flagValue = $evaluationDetail->getValue();After:
$evaluationDetail = $analyticsWrapper->variationDetail($flagKey, $context, $defaultValue);
$flagValue = $evaluationDetail->getValue();The wrapper automatically:
- Detects if the user is in an experiment
- Sends experiment data to Firehose/S3
- Returns the same
EvaluationDetailobject (fully backward compatible)
You can migrate incrementally - replace variationDetail() calls one at a time. The wrapper returns the same object type, so existing code continues to work.
The wrapper checks if a user is in an experiment by examining the EvaluationReason:
$reason = $evaluationDetail->getReason();
if ($reason->isInExperiment()) {
// Send to Firehose
}When an experiment is detected, the following data is sent to S3:
{
"timestamp": "2024-11-24T14:30:00+00:00",
"flag_key": "example-experiment-flag",
"evaluation_context": {
"key": "user-123",
"kind": "user",
"tier": "premium"
},
"flag_value": "treatment",
"variation_index": 1,
"reason_kind": "FALLTHROUGH",
"metadata": {
"source": "launchdarkly-php-wrapper",
"version": "1.0"
}
}hello-php/
├── README.md # This file
├── main.php # Example application
├── composer.json # PHP dependencies
├── env.example # Environment variable template
├── LICENSE.txt # License file
└── src/
├── FirehoseSender.php # AWS Firehose integration
└── VariationDetailAnalyticsWrapper.php # Wrapper class
Data is partitioned by year/month/day/hour for efficient querying:
s3://your-bucket/
├── experiments/
│ ├── year=2024/
│ │ ├── month=11/
│ │ │ ├── day=24/
│ │ │ │ ├── hour=14/
│ │ │ │ │ └── launchdarkly-experiments-stream-1-2024-11-24-14-00-00-abc123.json.gz
│ │ │ │ └── hour=15/
│ │ │ └── day=25/
│ │ └── month=12/
│ └── errors/
│ └── failed-records.json.gz
Note: Files are GZIP compressed and use newline-delimited JSON format (one event per line).
Each event contains:
{
"timestamp": "2024-11-24T14:30:00+00:00",
"flag_key": "example-experiment-flag",
"evaluation_context": {
"key": "user-123",
"kind": "user",
"tier": "premium",
"country": "US"
},
"flag_value": "treatment",
"variation_index": 1,
"reason_kind": "FALLTHROUGH",
"metadata": {
"source": "launchdarkly-php-wrapper",
"version": "1.0"
}
}Once your data is flowing to S3, configure your analytics platform to consume it.
See ../DATABRICKS_INTEGRATION.md for guidance on:
- Auto Loader configuration
- Sample analysis queries
- Performance optimization tips
Note: The Databricks integration guide is provided as a starting point and has not been tested. Please verify and adapt the configuration for your environment. This guide applies to data from both Python and PHP implementations since they produce the same S3 data format.
The S3 data is stored in a standard JSON format that can be consumed by:
- Snowflake - Use external tables or COPY commands
- BigQuery - Use external data sources
- Athena - Query directly from S3
- Custom applications - Use AWS SDKs to read the JSON files
# List files in your bucket
aws s3 ls s3://your-bucket-name/experiments/ --recursive
# Download and view a sample file
aws s3 cp s3://your-bucket-name/experiments/year=2024/month=11/day=24/hour=14/sample.json.gz /tmp/
gunzip -c /tmp/sample.json.gz | head -n 1 | jq .aws firehose describe-delivery-stream --delivery-stream-name launchdarkly-experiments-stream- Expired AWS credentials: Run
aws sso loginor refresh your credentials - Permission denied: Verify IAM role has proper S3 permissions
- Stream not found: Ensure Firehose delivery stream exists and is active
- Data not appearing:
- Firehose buffers data for up to 60 seconds
- Wait 1-5 minutes after sending events
- Check CloudWatch logs for delivery errors
- Monitor Firehose delivery metrics
- Set up alarms for failed deliveries
- Check S3 access logs for data arrival
The integration automatically captures any custom attributes in your LaunchDarkly contexts:
$context = LDContext::builder('user-123')
->kind('user')
->name('John Doe')
->set('tier', 'premium')
->set('country', 'US')
->set('subscription_id', 'sub_123')
->build();All custom attributes will be automatically included in the S3 data.
Edit src/FirehoseSender.php to customize the event structure:
$eventData = [
'timestamp' => date('c'),
'flag_key' => $flagKey,
'evaluation_context' => $this->extractContextData($context),
'flag_value' => $evaluationDetail->getValue(),
'variation_index' => $evaluationDetail->getVariationIndex(),
'reason_kind' => $this->getReasonKind($evaluationDetail),
// Add custom fields here
'custom_field' => 'custom_value',
'metadata' => [
'source' => 'launchdarkly-php-wrapper',
'version' => '1.0',
],
];- No Hooks: PHP SDK doesn't support hooks, so we use a wrapper pattern
- Manual Integration: Requires replacing
variationDetail()calls with the wrapper - Same Data Format: Event payload structure matches Python version for consistency
- Use IAM roles with minimal required permissions
- Enable S3 server-side encryption if needed
- Consider VPC endpoints for private network access
- Implement proper access controls for S3 buckets
- Use appropriate Firehose buffering settings
- Implement S3 lifecycle policies for data retention
- Consider data compression for large volumes
- Monitor and optimize partition sizes
This project is licensed under the Apache-2.0 License.