Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Flipping out with Feature Flags & Toggles - PHP...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Michael C.
April 08, 2017
Programming
180
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flipping out with Feature Flags & Toggles - PHP Yorkshire 2017
Michael C.
April 08, 2017
More Decks by Michael C.
See All by Michael C.
OOP & Design Patterns (Part 1 + Part 2)
michaelcullum
0
680
Building a first class REST API with Symfony
michaelcullum
3
2.1k
Trend Analysis and Machine Learning in PHP - PHP South Africa
michaelcullum
0
240
Hadoop & PHP - PHP South Africa
michaelcullum
0
180
Machine Learning and Trend Analysis in PHP - Cascadia PHP
michaelcullum
0
190
Trend Analysis & Machine Learning in PHP - PHP SW
michaelcullum
0
170
Machine Learning and Trend Analysis in PHP - DPC 18
michaelcullum
0
330
Trend Analysis & Machine Learning in PHP - PHP Serbia
michaelcullum
1
410
Machine Learning and Trend Analysis in PHP - DevDays Vilnius
michaelcullum
1
180
Other Decks in Programming
See All in Programming
1B+ /day規模のログを管理する技術
broadleaf
0
140
霧の中の代数的エフェクト
funnyycat
1
400
ルールを書いて終わらせないハーネスエンジニアリング
yug1224
3
1.5k
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.8k
LaravelLive Japan の裏方のすべて — 第188回 PHP勉強会@東京 (2026-06-24)
suguruooki
2
150
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
0
280
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
110
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
140
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
460
フィードバックで育てるAI開発
kotaminato
1
120
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
480
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
230
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
55
10k
Tell your own story through comics
letsgokoyo
1
1k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
220
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
150
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
180
Become a Pro
speakerdeck
PRO
31
6k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
How to build a perfect <img>
jonoalderson
1
5.8k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.4k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
180
Balancing Empowerment & Direction
lara
6
1.2k
Transcript
FLIPPING OUT WITH FEATURE FLAGS & TOGGLES PHP YORKSHIRE 2017
@MICHAELCULLUMUK
ME?
MICHAEL CULLUM @MICHAELCULLUMUK
BACK TO BASICS
CONDITIONALS <?php if (true) { oneAction(); } else { alternativeAction();
}
CONDITIONALS <?php if ($enabled) { oneAction(); } else { alternativeAction();
}
FEATURE FLAG <?php if (featureEnabled('twitter_seen')) { oneAction(); } else {
alternativeAction(); }
WHAT’S IN A NAME?
SWIFT MAILER
SWIFT MAILER swiftmailer: disable_delivery: ~
SWIFT MAILER <?php if (isset($mailer[‘disable_delivery'])) { $transport = 'null'; $container->setParameter('swiftmailer.enabled',
false); } else { $container->setParameter('swiftmailer.enabled', true); }
SWIFT MAILER <?php if (isset($mailer[‘disable_delivery'])) { $transport = 'null'; $container->setParameter('swiftmailer.enabled',
false); } else { $container->setParameter('swiftmailer.enabled', true); }
ALTERNATIVE TO BRANCHING?
VARY BY: ENVIRONMENT USER
DIFFERENT SUBSET OF USERS
A/B TESTING
BOOKING.COM MODEL
TEST IN PRODUCTION
MONOLITHIC REPOSITORIES
TEST A MATRIX OF CHANGES
MANAGING YOUR FLAGS
RETRIEVING FEATURE FLAG VALUES
SPLIT IT OUT <?php interface FeatureFlags { function isEnabled(string $flag):
boolean }
HARDCODED <?php function isEnabled(string $flag): boolean { if ($flag ==
'profile') { return true; } return false; } if (isEnabled('profile')) { newProfileMagic(); }
HARDCODED <?php function isEnabled(string $flag): boolean { if ($flag ==
'profile') { return true; } return false; } if (isEnabled('profile')) { newProfileMagic(); }
PARAMETERS IN CONFIG FILES <?php function isEnabled(string $flag): boolean {
if ($flag == 'mailDelivery') { return $this->container->getParameter('swiftmailer.enabled', $flag); } elseif (isset($this->container->getParameter(sprintf('flag.%s', $flag)))) { return $this->container->getParameter(sprintf('flag.%s', $flag)); } return false; }
CACHE function isEnabled(string $flag): boolean { $cache = $this->cache; $flag
= $cache->getItem(sprintf('flag.%s', $flag)); if (!$flag->isHit()) { $flag->get(); } return false; }
DATABASE
SETTING FEATURE FLAG VALUES
DATABASE
CODEBASE
USING THEM CLEANLY
TWIG {% if isFeatureEnabled('showCfpForm') %} {{ form(form) }} {% endif
%}
TWIG <?php namespace AppBundle\Twig; class AppExtension extends \Twig_Extension { public
function getFunctions() { return array( new \Twig_SimpleFunction('isEnabled', array($this, 'isEnabled')), ); } //.. }
COMPLEXITY <?php if ($container->getParameter('profileFeature')) { if ($container->getParameter('newProfileStyle')) { if ($container->getParameter('newProfileStyleButtons'))
{ enableButtons(); } if ($container->getParameter('myNewCoolProfilePic')) { renderPicture(); if ($container->getParameter('newProfileRenderEngine')) { tryDifferentRenderEngine(); } } renderNewProfiler(); } else { // Render the legacy profile } }
COMPLEXITY <?php if ($container->getParameter('profileFeature')) { if ($container->getParameter('newProfileStyle')) { if ($container->getParameter('newProfileStyleButtons'))
{ enableButtons(); } if ($container->getParameter('myNewCoolProfilePic')) { renderPicture(); if ($container->getParameter('newProfileRenderEngine')) { tryDifferentRenderEngine(); } } renderNewProfiler(); } else { // Render the legacy profile } }
COMPLEXITY <?php if ($container->getParameter('profileFeature')) { if ($container->getParameter('newProfileStyle')) { if ($container->getParameter('newProfileStyleButtons'))
{ enableButtons(); } if ($container->getParameter('myNewCoolProfilePic')) { renderPicture(); if ($container->getParameter('newProfileRenderEngine')) { tryDifferentRenderEngine(); } } renderNewProfiler(); } else { // Render the legacy profile } }
CONTROLLERS
CONTROLLERS - YAML profile: path: /profile/{user} defaults: _controller: DemoBundle:Profile:legacy _alt:
DemoBundle:Default:new _flag: newProfile
CONTROLLERS - EVENT <?php public function onKernelRequest($event) { $flag =
$event->getRequest->attributes->get('_flag'); $alternate = $event->getRequest->attributes->get('_alt'); if (isEnabled($flag)) { $event->getRequest->attributes->set(‘controller’, $alternate); } }
CONTROLLERS - EVENT <?php public function onKernelRequest($event) { $flag =
$event->getRequest->attributes->get(‘_flag'); $alternate = $event->getRequest->attributes->get(‘_alt'); if (isEnabled($flag)) { $event->getRequest->attributes->set(‘_controller’, $alternate); } if (!isset($alternate) && isset($flag) && !isEnabled($flag)) { throw new NotFoundHttpException(); } }
CONTROLLERS - EVENT <?php public function onKernelRequest($event) { $flag =
$event->getRequest->attributes->get(‘_flag'); $alternate = $event->getRequest->attributes->get(‘_alt'); if (isEnabled($flag)) { $event->getRequest->attributes->set(‘_controller’, $alternate); } if (!isset($alternate) && isset($flag) && !isEnabled($flag)) { throw new NotFoundHttpException(); } }
SERVICE FACTORIES
NON-GENERIC FACTORY <?php class ProfileFactory { public function create(): ProfileInterface
{ return isEnabled(‘newProfile’) ? $container->get(‘newProfile’) : $container->get(‘legacyProfile’); } }
GENERIC FLAGS FACTORY <?php class FlagsFactory { public function createService(string
$flag, $service, $alternate) { return isEnabled($flag) ? $container->get($service) : $container->get($alternate); } }
GENERIC FLAGS FACTORY services: app.flagsFactory: class: AppBundle\FlagsFactory app.profileManager: class: AppBundle\Profile\ProfileManager
factory: ['@app.flagsFactory', createService] arguments: - newProfile - legacyProfileManager - newProfileManager
REMOVE THEM LATER OR NOT?
ALLOWS YOU TO
BRANCHING WITHOUT CONFLICTS OR VCS
TEST IN PRODUCTION
SLOW ROLLOUT
FUNCTIONALITY ON DIFFERENT ENVIRONMENTS
A/B TESTING
CLEANLY
DATABASE OR CONFIGURATION FILES
AVOIDING LARGE CONDITIONALS
THANKS @MICHAELCULLUMUK
FLIPPING OUT WITH FEATURE FLAGS & TOGGLES PHP YORKSHIRE 2017
@MICHAELCULLUMUK