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
180
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
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
210
Creating Composable Callables in Contemporary C++
rollbear
0
160
Inside Stream API
skrb
1
770
AI 輔助遺留系統現代化的經驗分享
jame2408
1
970
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
610
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
160
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
300
The NotImplementedError Problem in Ruby
koic
1
920
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.8k
JavaDoc 再入門
nagise
1
410
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
160
Featured
See All Featured
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Unsuck your backbone
ammeep
672
58k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
590
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
230
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
430
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
560
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
630
Documentation Writing (for coders)
carmenintech
77
5.4k
Agile that works and the tools we love
rasmusluckow
331
22k
Un-Boring Meetings
codingconduct
0
320
Crafting Experiences
bethany
1
190
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