Powering Up PHP Feature Flags with OpenFeature
See just how easy it is to break free from vendor lock-in with OpenFeature in our latest blog post, where we detail the straightforward process of tweaking our PHP example app to embrace the OpenFeature standard.
Diving into the world of feature flags can sometimes seem daunting, especially when you're new to a feature flagging platform. Recognizing this challenge, in 2024 DevCycle embarked on a mission to develop some intuitive example applications—now available in the DevCycle Labs GitHub organization—designed to simplify the feature flagging process for newcomers.
Offering more than a dozen different flavours, these example applications now allow users to experiment with various features available through the DevCycle platform in a much more intuitive and user-friendly way.
While these new example applications are essential in welcoming new users to our platform, our team's dedication to the feature flagging ecosystem reaches well beyond merely attracting newcomers to DevCycle. We strive to foster the most effective use of feature flags. And so, in the rapidly growing world of feature management, this commitment translates into integrating OpenFeature.
Why focus on PHP for this OpenFeature conversion?
As a huge fan of PHP, thanks in part to its straightforward syntax and status as an extremely popular language for web development, I recently began the task of converting our example applications to implement the OpenFeature standard.
While PHP's syntax is distinct, the foundational work by our team on these example applications and the OpenFeature Providers ensures that much of the conversion process would be analogous across different languages and frameworks. This made PHP a prime candidate for a case study to explore the necessary adjustments for implementing OpenFeature effectively.
In our original PHP example application, we were working with four crucial and interconnected files:
- devcycle.php - The starting point where we establish our connection to the DevCycle platform and instantiate the DevCycle client.
- index.php - Where we define the
devcycle_client
anduser_data
variables used in our feature flag evaluation process. - togglebot.php & description.php - Where the magic happens:
- In
togglebot.php
, we evaluate a string and a boolean variable to animate the Togglebot image and its message.- Here we also implement the allFeatures hook, a native functionality of DevCycle, to showcase the "Serving Variation" block below the animated Togglebot.
- In
description.php
, a string variable controls the text displayed below the image, providing context to the tutorial's progression
- In
Moving from Vendor-locked to OpenFeature:
Here's a snippet from devcycle.php
in our original PHP example application to get us started:
use DevCycle\Api\DevCycleClient;
use DevCycle\Model\DevCycleOptions;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
$options = new DevCycleOptions();
$devcycle_client = new DevCycleClient(
sdkKey: $_ENV["DEVCYCLE_SERVER_SDK_KEY"],
dvcOptions: $options
);
To align with the OpenFeature standard, we had to make a few notable changes. First we needed to enable the OpenFeature API functionality in the DevCycle SDK through DevCycleOptions
object, and to instantiate the OpenFeature API itself. From there, we needed to set the Provider to DevCycle's OpenFeatureProvider
before finally instantiating the OpenFeature client for use in flag evaluations in our application.
use DevCycle\Api\DevCycleClient;
use DevCycle\Model\DevCycleOptions;
use OpenFeature\OpenFeatureAPI;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
$options = new DevCycleOptions(true); // Enabling OpenAPI features
$devcycle_client = new DevCycleClient(
sdkKey: $_ENV["DEVCYCLE_SERVER_SDK_KEY"],
dvcOptions: $options
);
$api = OpenFeatureAPI::getInstance();
$api->setProvider($devcycle_client->getOpenFeatureProvider());
$openfeature_client = $api->getClient();
Moving onto index.php
, our original example laid the groundwork for feature flag evaluations by creating a user-specific data object for use by the DevCycle SDK in the targeting process.
use DevCycle\Model\DevCycleUser;
$user_data = new DevCycleUser(array(
"user_id"=>"my-user"
));
The shift towards adopting OpenFeature, however, required a rethink in how we structure the data for use in feature flag evaluations. Rather than solely containing user-specific data for targeting, OpenFeature's EvaluationContext
object is designed to be data-agnostic, accommodating various types of information used in the flag evaluation process for targeting. With this in mind, we needed to turn to OpenFeature's Attributes
object in order to utilize our existing structure of user data, and feed it a digestible way to the OpenFeature API.
use OpenFeature\implementation\flags\EvaluationContext;
use OpenFeature\implementation\flags\Attributes;
use DevCycle\Model\DevCycleUser;
$user_attributes = new Attributes(array("user_id" => "my-user"));
$openfeature_context = new EvaluationContext(attributes: $user_attributes);
$devcycle_user_data = DevCycleUser::FromEvaluationContext($openfeature_context);
With much of the "heavy lifting" now complete in those other files, there were only a few minor changes remaining in togglebot.php
and description.php
to get OpenFeature functional. These primarily involved differences in the hooks used to retrieve the variable value in the DevCycle client versus the OpenFeature client.
To understand what I mean here, lets first look to the $step
variable in description.php
where our original example application relied on the variableValue
hook in DevCycle client to retrieve feature flag values:
$step = $devcycle_client->variableValue($user_data, "example-text", "default");
With the OpenFeature client, this hook becomes much more explicit, using getStringValue
for string-based feature flags.
$step = $openfeature_client->getStringValue("example-text", "default", $openfeature_context);
You may also notice another important change in this implementation around the ordering of parameters in these functions. Whereas the DevCycle hooks were ordered (user_data, variation_name, default_value)
in the OpenFeature context we input them as (variation_name, default_value, user_data)
.
We can also see this change in action in the $speed
variable in togglebot.php
. Here we again shifted from the variableValue
hook to getStringValue
and also introduced the (again more explicit) getBooleanValue
hook for $wink
:
// Original DevCycle Example Application
$speed = $devcycle_client->variableValue($user_data, "togglebot-speed", "off");
$wink = $devcycle_client->variableValue($user_data, "togglebot-wink", false);
// Revision for OpenFeature Provider Implementation
$speed = $openfeature_client->getStringValue("togglebot-speed", "off", $openfeature_context);
$wink = $openfeature_client->getBooleanValue("togglebot-wink", false, $openfeature_context);
Okay, but why maintain both clients?
You might wonder why we chose to keep both devcycle_client
and openfeature_client
in our updated example. The answer lies in togglebot.php
.
While OpenFeature provides a versatile framework for feature flagging, we encounter moments where DevCycle-specific functionalities, like events for A/B testing evaluation, are indispensable for full compatibility.
In our situation, the need for compatibility emerged around $features
. This variable in index.php
necessitated the allFeatures
hook to parse all features available for a user on the DevCycle platform, identify the feature with the hello-togglebot
key, and display the name of the variation being served.
$features = $devcycle_client->allFeatures($devcycle_user_data);
$variation_name = $features["hello-togglebot"]
? $features["hello-togglebot"]["variationName"]
: "Default";
Unfortunately, despite OpenFeature providing a powerful API, the functionality needed for this specific implementation is not yet available and as a result, we needed to maintain both clients for use in this demo application.
Are there any "gotchas" with this example?
Something to note in this setup is the integration within the standard PHP SDK for DevCycle, which already includes the OpenFeature SDK and the OpenFeature PHP Provider. This integration simplifies the process, eliminating the need for separate installations of the OpenFeature components. That said, the requirement for additional installations might vary based on the specific vendor you're utilizing.
That sounds straightforward, what's next?
As you can see, by making a few small adjustments to your current codebase (and with some assistance from your existing vendor in the form of an OpenFeature Provider) you can begin to future-proof your feature flag implementations. This approach not only enhances your system's adaptability but also ensures a smoother transition to newer technologies and standards in the future.
Where can I access your code?
Visit the DevCycle Labs GitHub organization to explore the repository for the new PHP OpenFeature example application and see these modifications applied in this tutorial in practice: