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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
110
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
130
なぜ型を書くのか? TSKaigi2026で改めて考える #tskaigi_smarthr
kajitack
0
380
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
0
210
symfony/aiとlaravel/boost
77web
0
130
エンジニアにデザインハーネスを 〜デザインプロセスを規定するためのハーネス〜 / Design harness from an engineer's perspective
rkaga
2
1.5k
Prismを使った型安全な暗号化_関数型まつり2026
_fhhmm
0
140
共通化で考えるべきは、実装より公開する型だった
codeegg
0
250
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
0
280
作るコストが小さくなった時代 幸せに働くために改めて考えたいこと 〜エンジニアとして価値を出し続けるために注視している二分野〜
yuppeeng
0
100
AI駆動開発を妨げる技術的負債の解消アプローチ / ai-refactoring-approach
minodriven
17
9.1k
1B+ /day規模のログを管理する技術
broadleaf
0
140
Featured
See All Featured
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.6k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
410
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
350
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
350
Abbi's Birthday
coloredviolet
3
8.7k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.7k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
190
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.4k
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