Imagine this: you’re working on a web platform with fixed deployment dates. Your video provider X notified you that they are switching to a new URL format for Y, and (due to the infrastructure) they cannot provide parallel operations of both versions. Nevertheless, you want to have a seamless switch to the new version without any downtime. They’ve estimated to be ready to switch in three weeks, but you need to test the handling of the new URL format on your stage system. On top of that, the next rollout is planned in two weeks! So what do you do?
You could arrange a time when you are changing it on all production servers manually. This is not only risky but makes it very difficult to test and develop. Now imagine, after the release, the provider notified you that you need to switch back to the old version. If you replaced the code, you now need to change it back and deploy it. Might there be another way to accomplish this?
Let’s grab a coffee and talk about: Feature flags!
First steps
“Oh alright, feature flags… got it!”
if ($featureIsEnabled) { doNewImplementation(); } else { doOldImplementation(); }
Done, right? Well, yes and no 😉. Where is the flag configured? Hardcoded? Config file? Environment variables? Database? Cookie? How to access the flag in a template? How can Non-Developers change it?
Many big companies use feature toggles including Flickr, Disqus, Etsy, Reddit, Gmail, and Netflix (according to https://en.wikipedia.org/wiki/Feature_toggle), and there’s a lot more to consider when thinking about feature flags. I can absolutely recommend you to read this article about Feature Toggles by Pete Hodgson: https://martinfowler.com/articles/feature-toggles.html
Done? Great! Now, let’s talk about a great library and Symfony bundle that will help you a lot when implementing Feature Flags in your PHP projects.
Flagception
SDK
Flagception SDK is a PHP feature toggle library with an additional Symfony bundle. It consists of a FeatureManager
and several possible feature activators (e.g., ArrayActivator
, EnvironmentActivator
or ChainActivator
) but you can also implement your own activators! This architecture makes the library highly flexible and adaptable to a lot of special use cases.
You can find a few code examples of the SDK on Github: https://github.com/bestit/flagception-sdk#advanced-example
Symfony Bundle
The Symfony bundle has a few additional helpers that will make your life easier! With the bundle, you can configure your feature toggles in a config file (https://github.com/bestit/flagception-bundle#quick-example), use it in Twig templates (https://github.com/bestit/flagception-bundle/blob/master/docs/twig.md), or view all feature toggles in the Symfony Profiler (https://github.com/bestit/flagception-bundle/blob/master/docs/profiler.md)!
Special features
Route flags
One of the greatest benefits for me is the ability to toggle routes by enabling or disabling a feature. Imagine developing a new feature with a new page, which should not be accessible until the feature is active. For example, a special “Black Friday” landing page offering your product at a much lower price, but the page should only be shown on Black Friday! So you can create a new feature, add a date
constraint and add the feature to the route’s annotations. If the feature is not active, the controller will return a NotFoundHttpException
which should result in a 404
page.
flagception: features: blackfriday2020: default: false constraint: 'date("d.m.Y") === "27.11.2020"' # add route attributes routing_metadata: enable: true
/** * @Route("/offer/blackfriday", defaults={"_feature": "blackfriday2020"}) */ public function blackfridayAction() { // ... }
Custom constraints
You have seen the date
constraint above already. There are a couple of constraints already implemented, but you can do two more cool things with the bundle:
- Define variables in the global context by using a Decorator (https://github.com/bestit/flagception-bundle/blob/master/docs/constraint.md#define-globally-variable). With this, you can inject, for example, the User’s id or roles into your constraint context and use it inside your constraint expressions.
- Define your own custom constraint methods by adding a provider to the Symfony expression language: https://github.com/bestit/flagception-bundle/blob/master/docs/constraint.md#define-own-methods
Doctrine activator
Maybe you want to enable/disable your flags in a nice admin interface and save the flag’s state in the database. That’s also possible with Flagception!
First, you need to install the database activator from composer and configure your database in the config file:
$ composer require flagception/database-activator
flagception: activators: database: enable: true # You can either use the connection string url: '%env(MYSQL_URL)%' # Or you can use the PDO/DBAL's service id pdo: 'pdo.service.id' dbal: 'dbal.service.id' # Rename table and columns options: db_table: 'feature_flag' db_column_feature: 'name' db_column_state: 'state'
Flagception has default table and column names, but you can also customize them as shown above. So let’s create a small entity for this and migrate it into our project:
<?php namespace App\Entity; use App\Repository\FeatureFlagRepository; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=FeatureFlagRepository::class) */ class FeatureFlag { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $name; /** * @ORM\Column(type="boolean") */ private $state; public function getId(): ?int { return $this->id; } public function getName(): ?string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getState(): ?bool { return $this->state; } public function setState(bool $state): self { $this->state = $state; return $this; } }
I decided to use a SonataAdmin for managing the feature flags here. You could argue that the id is not needed as the feature’s name should be unique, but I hope you can forgive me for the PoC (Proof of Concept) 😉
Troubleshooting / Best practices with Feature flags
With this bundle and the overall capability of feature toggles, you have a powerful tool in your hand, which can benefit you a lot but can also cause a lot of chaos. That is probably why there are already a lot of best practice guides online:
Here’s the summary in my own words:
Choose your flags wisely
Depending on the granularity, feature flags can become very complex, and it might get hard to maintain the overview.
So my tip is to think twice if you really need to add another feature flag. If you add a new feature flag, think about how long the flag will live inside your codebase and communicate or document this.
Naming Convention
Depending on your team size, it might be necessary to declare strict guidelines on how to name a new feature flag to quickly see its usage, where it belongs, what it switches on or off, or which type of feature toggle it is (https://martinfowler.com/articles/feature-toggles.html#CategoriesOfToggles). Imagine the nightmare of having to find out what feature_1 is affecting your code when you turn it off. Instead feature_banner_ads
might be a lot more self-explanatory.
My tip is to think about naming conventions early before you have 10 or 20 feature flags already in place, which need to be renamed afterward.
Access Control
As I have shown above, it is straightforward to make feature flags configurable via an admin interface. That being said, it might result in many editors or admins having access to these feature flags. When an error on your project occurs in the production stage, you need to analyze what caused the error quickly. Was it an external library? Did we deploy something? Could it be that a feature flag has been activated or deactivated that caused an error?
My tip is to think about who should be allowed to change feature flags and maybe even set rules for how changes are approved or communicated with your team.
Logging
That brings us to another crucial point! You should always be able to see who changed which feature flag when. This is important because you might need to comprehend afterward when a feature has been changed the last time or whom to contact to determine why a feature toggle has been changed.
If you’re using database activated features, my tip is to add a versioned entity, so you can see all the made changes and add a updatedBy
field to log the user who changed the field. For non-database flags, use git commits or some other way to document the changes.
Lifecycle management
Finally, a feature toggle might have been added to your project a long time ago and hasn’t been changed for more than one year. It might be time to remove this feature from the project. Feature flags should mostly stay in the project only for a short time period, so you don’t build up hundreds of thousands of different feature flags over the years!
As I’ve mentioned in “Logging”, it might be useful to log who updated the feature toggle, and additionally, I would recommend to log when a feature flag was created (createdAt
) and when it was updated the last time (updatedAt
).
Summary
Flagception is a really great tool to help to implement feature flags. If you’re using feature flags in PHP or Symfony, you should definitely give it a try!
Nevertheless, as Martin Fowler put it: Feature flags “[…] should be your last choice when you’re dealing with putting features into production.” (https://martinfowler.com/bliki/FeatureToggle.html). So I can only recommend you to read the article by Martin Fowler and the one by Pete Hodgson that I’ve already linked above: https://martinfowler.com/articles/feature-toggles.html.
I hope you enjoyed the read! Happy feature toggling everyone! 🙂