SlideShare a Scribd company logo
Decoupling with 
Design Patterns 
and Symfony DIC
@everzet 
· Spent more than 7 
years writing so!ware 
· Spent more than 4 
years learning 
businesses 
· Now filling the gaps 
between the two as a 
BDD Practice Manager 
@Inviqa
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
behat 3 
promise #1 (of 2): 
extensibility
“Extensibility is a so!ware design 
principle defined as a system’s ability 
to have new functionality extended, in 
which the system’s internal structure 
and data flow are minimally or not 
affected”
“So!ware entities (classes, modules, 
functions, etc.) should be open for 
extension, but closed for modification”
behat 3 
promise #2 (of 2): 
backwards compatibility
Decoupling with Design Patterns and Symfony2 DIC
behat 3 
- extensibility as the core concept 
- BC through extensibility
Symfony Bundles 
Behat extensions
Symfony Bundles & Behat extensions 
1. Framework creates a temporary 
container 
2. Framework asks the bundle to add its 
services 
3. Framework merges all temporary 
containers 
4. Framework compiles merged 
container
interface CompilerPassInterface 
{ 
/** 
* You can modify the container here before it is dumped to PHP code. 
* 
* @param ContainerBuilder $container 
* 
* @api 
*/ 
public function process(ContainerBuilder $container); 
}
class YourSuperBundle extends Bundle 
{ 
public function build(ContainerBuilder $container) 
{ 
parent::build($container); 
$container->addCompilerPass(new YourCompilerPass()); 
} 
}
v3.0 v1.0 
(extensibility solution v1)
challenge: 
behat as the most extensible 
test framework
pattern: observer
class HookDispatcher extends DispatchingService implements EventSubscriberInterface 
{ 
public static function getSubscribedEvents() 
{ 
return array( 
EventInterface::BEFORE_SUITE => array('dispatchHooks', 10), 
EventInterface::AFTER_SUITE => array('dispatchHooks', 10), 
EventInterface::BEFORE_FEATURE => array('dispatchHooks', 10), 
... 
); 
} 
public function dispatchHooks(LifecycleEventInterface $event) 
{ 
$hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); 
$this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); 
foreach ($hooksProvider->getHooksForEvent($event) as $hook) { 
$this->dispatchHook($hook, $event); 
} 
} 
... 
}
class HooksCarrierEvent extends Event implements LifecycleEventInterface 
{ 
public function addHook(HookInterface $hook) 
{ 
$this->hooks[] = $hook; 
} 
public function getHooksForEvent(Event $event) 
{ 
return array_filter( 
$this->hooks, 
function ($hook) use ($event) { 
$eventName = $event->getName(); 
if ($eventName !== $hook->getEventName()) { 
return false; 
} 
return $hook; 
} 
); 
} 
... 
}
class DictionaryReader implements EventSubscriberInterface 
{ 
public static function getSubscribedEvents() 
{ 
return array( 
EventInterface::LOAD_HOOKS => array('loadHooks', 0), 
... 
); 
} 
public function loadHooks(HooksCarrierEvent $event) 
{ 
foreach ($this->read($event->getSuite(), $event->getContextPool()) as $callback) { 
if ($callback instanceof HookInterface) { 
$event->addHook($callback); 
} 
} 
} 
... 
}
extension point
<container xmlns="http://symfony.com/schema/dic/services" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="..."> 
<services> 
<service id="event_dispatcher" 
class="SymfonyComponentEventDispatcherEventDispatcher"/> 
<service id="hook.hook_dispatcher" 
class="BehatBehatHookEventSubscriberHookDispatcher"> 
<argument type="service" id="event_dispatcher"/> 
<tag name="event_subscriber"/> 
</service> 
<service id="context.dictionary_reader" 
class="BehatBehatContextEventSubscriberDictionaryReader"> 
<tag name="event_subscriber"/> 
</service> 
</services> 
</container>
class EventSubscribersPass implements CompilerPassInterface 
{ 
public function process(ContainerBuilder $container) 
{ 
$dispatcherDefinition = $container->getDefinition('event_dispatcher'); 
foreach ($container->findTaggedServiceIds('event_subscriber') as $id => $attributes) { 
$dispatcherDefinition->addMethodCall('addSubscriber', array(new Reference($id))); 
} 
} 
}
where event dispatcher / 
observer is useful?
pub/sub as an 
architectural choice
“Coupling is a degree to which each 
program module relies on each one of 
the other modules”
“Cohesion is a degree to which the 
elements of a module belong together”
“Coupling is a degree to which each 
program module relies on each one of 
the other modules” 
public function dispatchHooks(LifecycleEventInterface $event) 
{ 
$hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); 
$this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); 
foreach ($hooksProvider->getHooksForEvent($event) as $hook) { 
$this->dispatchHook($hook, $event); 
} 
}
“Cohesion is a degree to which the 
elements of a module belong together” 
public function dispatchHooks(LifecycleEventInterface $event) 
{ 
$hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); 
$this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); 
foreach ($hooksProvider->getHooksForEvent($event) as $hook) { 
$this->dispatchHook($hook, $event); 
} 
}
Coupling ↓ 
Cohesion ↑
scratch that
v3.0 v2.0 
(extensibility solution v2)
There is no single solution for 
extensibility. Because extensibility is 
not a single problem
framework extensions 
Since v2.5 behat has some very 
important extensions: 
1. MinkExtension 
2. Symfony2Extension
problem: 
there are multiple possible 
algorithms for a single 
responsibility
pattern: delegation loop
final class EnvironmentManager 
{ 
private $handlers = array(); 
public function registerEnvironmentHandler(EnvironmentHandler $handler) 
{ 
$this->handlers[] = $handler; 
} 
public function buildEnvironment(Suite $suite) 
{ 
foreach ($this->handlers as $handler) { 
... 
} 
} 
public function isolateEnvironment(Environment $environment, $testSubject = null) 
{ 
foreach ($this->handlers as $handler) { 
... 
} 
} 
}
interface EnvironmentHandler 
{ 
public function supportsSuite(Suite $suite); 
public function buildEnvironment(Suite $suite); 
public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null); 
public function isolateEnvironment(Environment $environment, $testSubject = null); 
}
extension point
<container xmlns="http://symfony.com/schema/dic/services" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="..."> 
<services> 
<service id=“environment.manager” 
class="BehatTestworkEnvironmentEnvironmentManager” /> 
<service id=“behat.context.environment.handler” 
class=“BehatBehatContextEnvironmentContextEnvironmentHandler”> 
<tag name=“environment.handler”/> 
</service> 
</services> 
</container>
final class EnvironmentHandlerPass implements CompilerPassInterface 
{ 
public function process(ContainerBuilder $container) 
{ 
$references = $this->processor->findAndSortTaggedServices($container, ‘environment.handler’); 
$definition = $container->getDefinition(‘environment.manager’); 
foreach ($references as $reference) { 
$definition->addMethodCall('registerEnvironmentHandler', array($reference)); 
} 
} 
}
where delegation loop is 
useful?
behat testers 
There are 5 testers in behat core: 
1. FeatureTester 
2. ScenarioTester 
3. OutlineTester 
4. BackgroundTester 
5. StepTester
behat testers 
Behat needs to provide you with: 
· Hooks 
· Events
problem: 
we need to dynamically extend 
the core testers behaviour
pattern: decorator
final class RuntimeScenarioTester implements ScenarioTester 
{ 
public function setUp(Environment $env, FeatureNode $feature, 
Scenario $example, $skip) 
{ 
return new SuccessfulSetup(); 
} 
public function test(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip = false) 
{ 
... 
} 
public function tearDown(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip, TestResult $result) 
{ 
return new SuccessfulTeardown(); 
} 
}
interface ScenarioTester 
{ 
public function setUp(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip); 
public function test(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip); 
public function tearDown(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip, TestResult $result); 
}
final class EventDispatchingScenarioTester implements ScenarioTester 
{ 
public function __construct(ScenarioTester $baseTester, EventDispatcherInterface $eventDispatcher) 
{ 
$this->baseTester = $baseTester; 
$this->eventDispatcher = $eventDispatcher; 
} 
public function setUp(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip) 
{ 
$event = new BeforeScenarioTested($env, $feature, $scenario); 
$this->eventDispatcher->dispatch($this->beforeEventName, $event); 
$setup = $this->baseTester->setUp($env, $feature, $scenario, $skip); 
return $setup; 
} 
public function test(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip) 
{ 
return $this->baseTester->test($env, $feature, $scenario, $skip); 
} 
public function tearDown(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip, TestResult $result) 
{ 
$teardown = $this->baseTester->tearDown($env, $feature, $scenario, $skip, $result); 
$event = new AfterScenarioTested($env, $feature, $scenario, $result, $teardown); 
$this->eventDispatcher->dispatch($event); 
return $teardown; 
} 
}
final class HookableScenarioTester implements ScenarioTester 
{ 
public function __construct(ScenarioTester $baseTester, HookDispatcher $hookDispatcher) 
{ 
$this->baseTester = $baseTester; 
$this->hookDispatcher = $hookDispatcher; 
} 
public function setUp(Environment $env, FeatureNode $feature, 
Scenario $example, $skip) 
{ 
$setup = $this->baseTester->setUp($env, $feature, $scenario, $skip); 
$hookCallResults = $this->hookDispatcher->dispatchScopeHooks($setup); 
return new HookedSetup($setup, $hookCallResults); 
} 
public function test(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip = false) 
{ 
return $this->baseTester->test($env, $feature, $scenario, $skip); 
} 
public function tearDown(Environment $env, FeatureNode $feature, 
Scenario $scenario, $skip, TestResult $result) 
{ 
$teardown = $this->baseTester->tearDown($env, $feature, $scenario, $skip, $result); 
$hookCallResults = $this->hookDispatcher->dispatchScopeHooks($teardown); 
return new HookedTeardown($teardown, $hookCallResults); 
} 
}
extension point
<container xmlns="http://symfony.com/schema/dic/services" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="..."> 
<services> 
<service id=“tester.scenario” 
class="BehatBehatTesterScenarioTester” /> 
<service id=“hooks.tester.scenario” 
class=“BehatBehatHooksTesterScenarioTester”> 
... 
<tag name=“tester.scenario_wrapper” order=“100”/> 
</service> 
<service id=“events.tester.scenario” 
class=“BehatBehatEventsTesterScenarioTester”> 
... 
<tag name=“tester.scenario_wrapper” order=“200”/> 
</service> 
</services> 
</container>
final class ScenarioTesterWrappersPass implements CompilerPassInterface 
{ 
public function process(ContainerBuilder $container) 
{ 
$references = $this->findAndReorderTaggedServices($container, ‘tester.scenario_wrapper’); 
foreach ($references as $reference) { 
$id = (string) $reference; 
$renamedId = $id . '.inner'; 
// This logic is based on SymfonyComponentDependencyInjectionCompilerDecoratorServicePass 
$definition = $container->getDefinition(‘tester.scenario’); 
$container->setDefinition($renamedId, $definition); 
$container->setAlias('tester.scenario', new Alias($id, $public)); 
$wrappingService = $container->getDefinition($id); 
$wrappingService->replaceArgument(0, new Reference($renamedId)); 
} 
} 
... 
}
where decorator is useful?
behat output 
Behat has a very simple output:
behat output 
Until you start using backgrounds:
behat output 
And throwing exceptions from their 
hooks:
problem: 
we need to add behaviour to 
complex output logic
pattern: observer
pattern: chain of responsibility
pattern: composite
final class NodeEventListeningFormatter implements Formatter 
{ 
public function __construct(EventListener $listener) 
{ 
$this->listener = $listener; 
} 
public static function getSubscribedEvents() 
{ 
return array(TestworkEventDispatcher::BEFORE_ALL_EVENTS => 'listenEvent'); 
} 
public function listenEvent(Event $event, $eventName = null) 
{ 
$eventName = $eventName ?: $event->getName(); 
$this->listener->listenEvent($this, $event, $eventName); 
} 
}
final class ChainEventListener implements EventListener, Countable, IteratorAggregate 
{ 
private $listeners; 
public function __construct(array $listeners) 
{ 
$this->listeners = $listeners; 
} 
public function listenEvent(Formatter $formatter, Event $event, $eventName) 
{ 
foreach ($this->listeners as $listener) { 
$listener->listenEvent($formatter, $event, $eventName); 
} 
} 
... 
}
Event listeners 
Behat has 2 types of listeners: 
1. Printers 
2. Flow controllers
final class StepListener implements EventListener 
{ 
public function listenEvent(Formatter $formatter, Event $event, $eventName) 
{ 
$this->captureScenarioOnScenarioEvent($event); 
$this->forgetScenarioOnAfterEvent($eventName); 
$this->printStepSetupOnBeforeEvent($formatter, $event); 
$this->printStepOnAfterEvent($formatter, $event); 
} 
... 
}
How do backgrounds work?
class FirstBackgroundFiresFirstListener implements EventListener 
{ 
public function __construct(EventListener $descendant) 
{ 
$this->descendant = $descendant; 
} 
public function listenEvent(Formatter $formatter, Event $event, $eventName) 
{ 
$this->flushStatesIfBeginningOfTheFeature($eventName); 
$this->markFirstBackgroundPrintedAfterBackground($eventName); 
if ($this->isEventDelayedUntilFirstBackgroundPrinted($event)) { 
$this->delayedUntilBackgroundEnd[] = array($event, $eventName); 
return; 
} 
$this->descendant->listenEvent($formatter, $event, $eventName); 
$this->fireDelayedEventsOnAfterBackground($formatter, $eventName); 
} 
}
where composite and CoR 
are useful?
Decoupling with Design Patterns and Symfony2 DIC
interface StepTester 
{ 
public function setUp(Environment $env, FeatureNode $feature, 
StepNode $step, $skip); 
public function test(Environment $env, FeatureNode $feature, 
StepNode $step, $skip); 
public function tearDown(Environment $env, FeatureNode $feature, 
StepNode $step, $skip, StepResult $result); 
}
problem: 
we need to introduce 
backwards incompatible 
change into the API
pattern: adapter
interface ScenarioStepTester 
{ 
public function setUp(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip); 
public function test(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip); 
public function tearDown(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip, 
StepResult $result); 
}
final class StepToScenarioTesterAdapter implements ScenarioStepTester 
{ 
public function __construct(StepTester $stepTester) { ... } 
public function setUp(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip) 
{ 
return $this->stepTester->setUp($env, $feature, $step, $skip); 
} 
public function test(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip) 
{ 
return $this->stepTester->test($env, $feature, $step, $skip); 
} 
public function tearDown(Environment $env, FeatureNode $feature, 
ScenarioNode $scenario, StepNode $step, $skip, 
StepResult $result) 
{ 
return $this->stepTester-> tearDown($env, $feature, $step, $skip); 
} 
}
final class StepTesterAdapterPass implements CompilerPassInterface 
{ 
public function process(ContainerBuilder $container) 
{ 
$references = $this->processor->findAndSortTaggedServices($container, ‘tester.step_wrapper’); 
foreach ($references as $reference) { 
$id = (string) $reference; 
$renamedId = $id . ‘.adaptee’; 
$adapteeDefinition = $container->getDefinition($id); 
$reflection = new ReflectionClass($adapteeDefinition->getClass()); 
if (!$reflection->implementsInterface(‘StepTester’)) { 
return; 
} 
$container->removeDefinition($id); 
$container->setDefinition( 
$id, 
new Definition(‘StepToScenarioTesterAdapter’, array( 
$adapteeDefinition 
)); 
); 
} 
} 
}
where adapter is useful?
demo
backwards 
compatibility
backwards compatibility 
Backwards compatibility in Behat 
comes from the extensibility. 
1. Everything is extension 
2. New features are extensions too 
3. New features could be toggled on/off
performance 
implications
performance implications 
· 2x more objects in v3 than in v2 
· Value objects are used instead of 
simple types 
· A lot of additional concepts 
throughout 
· It must be slow
yet...
Decoupling with Design Patterns and Symfony2 DIC
how?
how? 
immutability!
TestWork
TestWork
how?
Step1: Close the doors 
Assume you have no extension points 
by default. 
1. Private properties 
2. Final classes
Step 2: Open doors properly when you need them 
1. Identify the need for extension points 
2. Make extension points explicit
Private properties 
...
Final classes
class BundleFeatureLocator extends FilesystemFeatureLocator 
{ 
public function locateSpecifications(Suite $suite, $locator) 
{ 
if (!$suite instanceof SymfonyBundleSuite) { 
return new noSpecificationsIterator($suite); 
} 
$bundle = $suite->getBundle(); 
if (0 !== strpos($locator, '@' . $bundle->getName())) { 
return new NoSpecificationsIterator($suite); 
} 
$locatorSuffix = substr($locator, strlen($bundle->getName()) + 1); 
return parent::locateSpecifications($suite, $bundle->getPath() . '/Features' . $locatorSuffix); 
} 
}
final class BundleFeatureLocator implements SpecificationLocator 
{ 
public function __construct(SpecificationLocator $baseLocator) { ... } 
public function locateSpecifications(Suite $suite, $locator) 
{ 
if (!$suite instanceof SymfonyBundleSuite) { 
return new noSpecificationsIterator($suite); 
} 
$bundle = $suite->getBundle(); 
if (0 !== strpos($locator, '@' . $bundle->getName())) { 
return new NoSpecificationsIterator($suite); 
} 
$locatorSuffix = substr($locator, strlen($bundle->getName()) + 1); 
return $this->baseLocator->locateSpecifications($suite, $bundle->getPath() . '/Features' . $locatorSuffix); 
} 
}
the most closed most 
extensible testing 
framework
ask questions 
close Feed! L♻♻ps: 
https://joind.in/11559
Ad

Recommended

Min-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
Design how your objects talk through mocking
Design how your objects talk through mocking
Konstantin Kudryashov
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
Konstantin Kudryashov
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
The IoC Hydra
The IoC Hydra
Kacper Gunia
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Kacper Gunia
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
Kacper Gunia
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Frontin like-a-backer
Frontin like-a-backer
Frank de Jonge
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
Aleix Vergés
 
Silex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Database Design Patterns
Database Design Patterns
Hugo Hamon
 
The History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
G Woo
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
Javier Eguiluz
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 
Doctrine fixtures
Doctrine fixtures
Bill Chang
 
Mocking Demystified
Mocking Demystified
Marcello Duarte
 
The Zen of Lithium
The Zen of Lithium
Nate Abele
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve
 
Doctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
Samuel ROZE
 
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Giulio De Donato
 
Design patterns avec Symfony
Design patterns avec Symfony
Mohammed Rhamnia
 

More Related Content

What's hot (20)

Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Frontin like-a-backer
Frontin like-a-backer
Frank de Jonge
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
Aleix Vergés
 
Silex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Database Design Patterns
Database Design Patterns
Hugo Hamon
 
The History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
G Woo
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
Javier Eguiluz
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 
Doctrine fixtures
Doctrine fixtures
Bill Chang
 
Mocking Demystified
Mocking Demystified
Marcello Duarte
 
The Zen of Lithium
The Zen of Lithium
Nate Abele
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve
 
Doctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
Samuel ROZE
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
Aleix Vergés
 
Silex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Database Design Patterns
Database Design Patterns
Hugo Hamon
 
The History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
G Woo
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
Javier Eguiluz
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 
Doctrine fixtures
Doctrine fixtures
Bill Chang
 
The Zen of Lithium
The Zen of Lithium
Nate Abele
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve
 
Doctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
Samuel ROZE
 

Viewers also liked (20)

Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Giulio De Donato
 
Design patterns avec Symfony
Design patterns avec Symfony
Mohammed Rhamnia
 
Moving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Modern Agile Project Toolbox
Modern Agile Project Toolbox
Konstantin Kudryashov
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code (AgileCymru)
Konstantin Kudryashov
 
Taking back BDD
Taking back BDD
Konstantin Kudryashov
 
Understanding craftsmanship
Understanding craftsmanship
Marcello Duarte
 
How Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith
 
BDD by example
BDD by example
Xavier Fornés Arrabal
 
Modern Project Toolbox
Modern Project Toolbox
Konstantin Kudryashov
 
Being effective with legacy projects
Being effective with legacy projects
Konstantin Kudryashov
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
Konstantin Kudryashov
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
Konstantin Kudryashov
 
BDD для PHP проектов
BDD для PHP проектов
Konstantin Kudryashov
 
Data Flow Patterns in Angular 2 - Sebastian Müller
Data Flow Patterns in Angular 2 - Sebastian Müller
Sebastian Holstein
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
Konstantin Kudryashov
 
Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3
Fabien Potencier
 
Moving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
James Titcumb
 
Symfony2: 30 astuces et bonnes pratiques
Symfony2: 30 astuces et bonnes pratiques
Noel GUILBERT
 
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Giulio De Donato
 
Design patterns avec Symfony
Design patterns avec Symfony
Mohammed Rhamnia
 
Moving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code (AgileCymru)
Konstantin Kudryashov
 
Understanding craftsmanship
Understanding craftsmanship
Marcello Duarte
 
How Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith
 
Being effective with legacy projects
Being effective with legacy projects
Konstantin Kudryashov
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
Konstantin Kudryashov
 
Data Flow Patterns in Angular 2 - Sebastian Müller
Data Flow Patterns in Angular 2 - Sebastian Müller
Sebastian Holstein
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
Konstantin Kudryashov
 
Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3
Fabien Potencier
 
Moving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
James Titcumb
 
Symfony2: 30 astuces et bonnes pratiques
Symfony2: 30 astuces et bonnes pratiques
Noel GUILBERT
 
Ad

Similar to Decoupling with Design Patterns and Symfony2 DIC (20)

Hooks and Events in Drupal 8
Hooks and Events in Drupal 8
Nida Ismail Shah
 
The state of hooking into Drupal - DrupalCon Dublin
The state of hooking into Drupal - DrupalCon Dublin
Nida Ismail Shah
 
Symfony 2
Symfony 2
Kris Wallsmith
 
Symfony Live San Franciso 2017 - BDD API Development with Symfony and Behat
Symfony Live San Franciso 2017 - BDD API Development with Symfony and Behat
Adam Englander
 
Dependency Injection in Drupal 8
Dependency Injection in Drupal 8
katbailey
 
Design patterns illustrated-2015-03
Design patterns illustrated-2015-03
Herman Peeren
 
Behavioural Driven Development in Zf2
Behavioural Driven Development in Zf2
David Contavalli
 
TDD with PhpSpec
TDD with PhpSpec
CiaranMcNulty
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
Symfony2 your way
Symfony2 your way
Rafał Wrzeszcz
 
Symfony War Stories
Symfony War Stories
Jakub Zalas
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - Limenius
Ignacio Martín
 
Symfony2 Specification by examples
Symfony2 Specification by examples
Corley S.r.l.
 
How I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
From silex to symfony and viceversa
From silex to symfony and viceversa
Ronny López
 
How I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
Jakub Zalas
 
Beyond MVC: from Model to Domain
Beyond MVC: from Model to Domain
Jeremy Cook
 
Specking Interactors with PHPSpec and YOLO (DDD) at PHPConference Argentina 2013
Specking Interactors with PHPSpec and YOLO (DDD) at PHPConference Argentina 2013
cordoval
 
The command dispatcher pattern
The command dispatcher pattern
olvlvl
 
Hooks and Events in Drupal 8
Hooks and Events in Drupal 8
Nida Ismail Shah
 
The state of hooking into Drupal - DrupalCon Dublin
The state of hooking into Drupal - DrupalCon Dublin
Nida Ismail Shah
 
Symfony Live San Franciso 2017 - BDD API Development with Symfony and Behat
Symfony Live San Franciso 2017 - BDD API Development with Symfony and Behat
Adam Englander
 
Dependency Injection in Drupal 8
Dependency Injection in Drupal 8
katbailey
 
Design patterns illustrated-2015-03
Design patterns illustrated-2015-03
Herman Peeren
 
Behavioural Driven Development in Zf2
Behavioural Driven Development in Zf2
David Contavalli
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
Symfony War Stories
Symfony War Stories
Jakub Zalas
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - Limenius
Ignacio Martín
 
Symfony2 Specification by examples
Symfony2 Specification by examples
Corley S.r.l.
 
How I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
From silex to symfony and viceversa
From silex to symfony and viceversa
Ronny López
 
How I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
Jakub Zalas
 
Beyond MVC: from Model to Domain
Beyond MVC: from Model to Domain
Jeremy Cook
 
Specking Interactors with PHPSpec and YOLO (DDD) at PHPConference Argentina 2013
Specking Interactors with PHPSpec and YOLO (DDD) at PHPConference Argentina 2013
cordoval
 
The command dispatcher pattern
The command dispatcher pattern
olvlvl
 
Ad

Recently uploaded (20)

Simplify Insurance Regulations with Compliance Management Software
Simplify Insurance Regulations with Compliance Management Software
Insurance Tech Services
 
Decipher SEO Solutions for your startup needs.
Decipher SEO Solutions for your startup needs.
mathai2
 
Canva Pro Crack Free Download 2025-FREE LATEST
Canva Pro Crack Free Download 2025-FREE LATEST
grete1122g
 
Threat Modeling a Batch Job Framework - Teri Radichel - AWS re:Inforce 2025
Threat Modeling a Batch Job Framework - Teri Radichel - AWS re:Inforce 2025
2nd Sight Lab
 
Introduction to Agile Frameworks for Product Managers.pdf
Introduction to Agile Frameworks for Product Managers.pdf
Ali Vahed
 
Best AI-Powered Wearable Tech for Remote Health Monitoring in 2025
Best AI-Powered Wearable Tech for Remote Health Monitoring in 2025
SEOLIFT - SEO Company London
 
Simplify Task, Team, and Project Management with Orangescrum Work
Simplify Task, Team, and Project Management with Orangescrum Work
Orangescrum
 
Why Every Growing Business Needs a Staff Augmentation Company IN USA.pdf
Why Every Growing Business Needs a Staff Augmentation Company IN USA.pdf
mary rojas
 
Top Time Tracking Solutions for Accountants
Top Time Tracking Solutions for Accountants
oliviareed320
 
Digital Transformation: Automating the Placement of Medical Interns
Digital Transformation: Automating the Placement of Medical Interns
Safe Software
 
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
arabelatso
 
IObit Driver Booster Pro 12 Crack Latest Version Download
IObit Driver Booster Pro 12 Crack Latest Version Download
pcprocore
 
Best Practice for LLM Serving in the Cloud
Best Practice for LLM Serving in the Cloud
Alluxio, Inc.
 
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
arabelatso
 
Modern Platform Engineering with Choreo - The AI-Native Internal Developer Pl...
Modern Platform Engineering with Choreo - The AI-Native Internal Developer Pl...
WSO2
 
Microsoft-365-Administrator-s-Guide1.pdf
Microsoft-365-Administrator-s-Guide1.pdf
mazharatknl
 
ElectraSuite_Prsentation(online voting system).pptx
ElectraSuite_Prsentation(online voting system).pptx
mrsinankhan01
 
Which Hiring Management Tools Offer the Best ROI?
Which Hiring Management Tools Offer the Best ROI?
HireME
 
OpenChain Webinar - AboutCode - Practical Compliance in One Stack – Licensing...
OpenChain Webinar - AboutCode - Practical Compliance in One Stack – Licensing...
Shane Coughlan
 
ERP Systems in the UAE: Driving Business Transformation with Smart Solutions
ERP Systems in the UAE: Driving Business Transformation with Smart Solutions
dheeodoo
 
Simplify Insurance Regulations with Compliance Management Software
Simplify Insurance Regulations with Compliance Management Software
Insurance Tech Services
 
Decipher SEO Solutions for your startup needs.
Decipher SEO Solutions for your startup needs.
mathai2
 
Canva Pro Crack Free Download 2025-FREE LATEST
Canva Pro Crack Free Download 2025-FREE LATEST
grete1122g
 
Threat Modeling a Batch Job Framework - Teri Radichel - AWS re:Inforce 2025
Threat Modeling a Batch Job Framework - Teri Radichel - AWS re:Inforce 2025
2nd Sight Lab
 
Introduction to Agile Frameworks for Product Managers.pdf
Introduction to Agile Frameworks for Product Managers.pdf
Ali Vahed
 
Best AI-Powered Wearable Tech for Remote Health Monitoring in 2025
Best AI-Powered Wearable Tech for Remote Health Monitoring in 2025
SEOLIFT - SEO Company London
 
Simplify Task, Team, and Project Management with Orangescrum Work
Simplify Task, Team, and Project Management with Orangescrum Work
Orangescrum
 
Why Every Growing Business Needs a Staff Augmentation Company IN USA.pdf
Why Every Growing Business Needs a Staff Augmentation Company IN USA.pdf
mary rojas
 
Top Time Tracking Solutions for Accountants
Top Time Tracking Solutions for Accountants
oliviareed320
 
Digital Transformation: Automating the Placement of Medical Interns
Digital Transformation: Automating the Placement of Medical Interns
Safe Software
 
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
arabelatso
 
IObit Driver Booster Pro 12 Crack Latest Version Download
IObit Driver Booster Pro 12 Crack Latest Version Download
pcprocore
 
Best Practice for LLM Serving in the Cloud
Best Practice for LLM Serving in the Cloud
Alluxio, Inc.
 
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
CodeCleaner: Mitigating Data Contamination for LLM Benchmarking
arabelatso
 
Modern Platform Engineering with Choreo - The AI-Native Internal Developer Pl...
Modern Platform Engineering with Choreo - The AI-Native Internal Developer Pl...
WSO2
 
Microsoft-365-Administrator-s-Guide1.pdf
Microsoft-365-Administrator-s-Guide1.pdf
mazharatknl
 
ElectraSuite_Prsentation(online voting system).pptx
ElectraSuite_Prsentation(online voting system).pptx
mrsinankhan01
 
Which Hiring Management Tools Offer the Best ROI?
Which Hiring Management Tools Offer the Best ROI?
HireME
 
OpenChain Webinar - AboutCode - Practical Compliance in One Stack – Licensing...
OpenChain Webinar - AboutCode - Practical Compliance in One Stack – Licensing...
Shane Coughlan
 
ERP Systems in the UAE: Driving Business Transformation with Smart Solutions
ERP Systems in the UAE: Driving Business Transformation with Smart Solutions
dheeodoo
 

Decoupling with Design Patterns and Symfony2 DIC

  • 1. Decoupling with Design Patterns and Symfony DIC
  • 2. @everzet · Spent more than 7 years writing so!ware · Spent more than 4 years learning businesses · Now filling the gaps between the two as a BDD Practice Manager @Inviqa
  • 6. behat 3 promise #1 (of 2): extensibility
  • 7. “Extensibility is a so!ware design principle defined as a system’s ability to have new functionality extended, in which the system’s internal structure and data flow are minimally or not affected”
  • 8. “So!ware entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”
  • 9. behat 3 promise #2 (of 2): backwards compatibility
  • 11. behat 3 - extensibility as the core concept - BC through extensibility
  • 12. Symfony Bundles Behat extensions
  • 13. Symfony Bundles & Behat extensions 1. Framework creates a temporary container 2. Framework asks the bundle to add its services 3. Framework merges all temporary containers 4. Framework compiles merged container
  • 14. interface CompilerPassInterface { /** * You can modify the container here before it is dumped to PHP code. * * @param ContainerBuilder $container * * @api */ public function process(ContainerBuilder $container); }
  • 15. class YourSuperBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new YourCompilerPass()); } }
  • 16. v3.0 v1.0 (extensibility solution v1)
  • 17. challenge: behat as the most extensible test framework
  • 19. class HookDispatcher extends DispatchingService implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( EventInterface::BEFORE_SUITE => array('dispatchHooks', 10), EventInterface::AFTER_SUITE => array('dispatchHooks', 10), EventInterface::BEFORE_FEATURE => array('dispatchHooks', 10), ... ); } public function dispatchHooks(LifecycleEventInterface $event) { $hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); $this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); foreach ($hooksProvider->getHooksForEvent($event) as $hook) { $this->dispatchHook($hook, $event); } } ... }
  • 20. class HooksCarrierEvent extends Event implements LifecycleEventInterface { public function addHook(HookInterface $hook) { $this->hooks[] = $hook; } public function getHooksForEvent(Event $event) { return array_filter( $this->hooks, function ($hook) use ($event) { $eventName = $event->getName(); if ($eventName !== $hook->getEventName()) { return false; } return $hook; } ); } ... }
  • 21. class DictionaryReader implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( EventInterface::LOAD_HOOKS => array('loadHooks', 0), ... ); } public function loadHooks(HooksCarrierEvent $event) { foreach ($this->read($event->getSuite(), $event->getContextPool()) as $callback) { if ($callback instanceof HookInterface) { $event->addHook($callback); } } } ... }
  • 23. <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..."> <services> <service id="event_dispatcher" class="SymfonyComponentEventDispatcherEventDispatcher"/> <service id="hook.hook_dispatcher" class="BehatBehatHookEventSubscriberHookDispatcher"> <argument type="service" id="event_dispatcher"/> <tag name="event_subscriber"/> </service> <service id="context.dictionary_reader" class="BehatBehatContextEventSubscriberDictionaryReader"> <tag name="event_subscriber"/> </service> </services> </container>
  • 24. class EventSubscribersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $dispatcherDefinition = $container->getDefinition('event_dispatcher'); foreach ($container->findTaggedServiceIds('event_subscriber') as $id => $attributes) { $dispatcherDefinition->addMethodCall('addSubscriber', array(new Reference($id))); } } }
  • 25. where event dispatcher / observer is useful?
  • 26. pub/sub as an architectural choice
  • 27. “Coupling is a degree to which each program module relies on each one of the other modules”
  • 28. “Cohesion is a degree to which the elements of a module belong together”
  • 29. “Coupling is a degree to which each program module relies on each one of the other modules” public function dispatchHooks(LifecycleEventInterface $event) { $hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); $this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); foreach ($hooksProvider->getHooksForEvent($event) as $hook) { $this->dispatchHook($hook, $event); } }
  • 30. “Cohesion is a degree to which the elements of a module belong together” public function dispatchHooks(LifecycleEventInterface $event) { $hooksProvider = new HooksCarrierEvent($event->getSuite(), $event->getContextPool()); $this->dispatch(EventInterface::LOAD_HOOKS, $hooksProvider); foreach ($hooksProvider->getHooksForEvent($event) as $hook) { $this->dispatchHook($hook, $event); } }
  • 33. v3.0 v2.0 (extensibility solution v2)
  • 34. There is no single solution for extensibility. Because extensibility is not a single problem
  • 35. framework extensions Since v2.5 behat has some very important extensions: 1. MinkExtension 2. Symfony2Extension
  • 36. problem: there are multiple possible algorithms for a single responsibility
  • 38. final class EnvironmentManager { private $handlers = array(); public function registerEnvironmentHandler(EnvironmentHandler $handler) { $this->handlers[] = $handler; } public function buildEnvironment(Suite $suite) { foreach ($this->handlers as $handler) { ... } } public function isolateEnvironment(Environment $environment, $testSubject = null) { foreach ($this->handlers as $handler) { ... } } }
  • 39. interface EnvironmentHandler { public function supportsSuite(Suite $suite); public function buildEnvironment(Suite $suite); public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null); public function isolateEnvironment(Environment $environment, $testSubject = null); }
  • 41. <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..."> <services> <service id=“environment.manager” class="BehatTestworkEnvironmentEnvironmentManager” /> <service id=“behat.context.environment.handler” class=“BehatBehatContextEnvironmentContextEnvironmentHandler”> <tag name=“environment.handler”/> </service> </services> </container>
  • 42. final class EnvironmentHandlerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $references = $this->processor->findAndSortTaggedServices($container, ‘environment.handler’); $definition = $container->getDefinition(‘environment.manager’); foreach ($references as $reference) { $definition->addMethodCall('registerEnvironmentHandler', array($reference)); } } }
  • 43. where delegation loop is useful?
  • 44. behat testers There are 5 testers in behat core: 1. FeatureTester 2. ScenarioTester 3. OutlineTester 4. BackgroundTester 5. StepTester
  • 45. behat testers Behat needs to provide you with: · Hooks · Events
  • 46. problem: we need to dynamically extend the core testers behaviour
  • 48. final class RuntimeScenarioTester implements ScenarioTester { public function setUp(Environment $env, FeatureNode $feature, Scenario $example, $skip) { return new SuccessfulSetup(); } public function test(Environment $env, FeatureNode $feature, Scenario $scenario, $skip = false) { ... } public function tearDown(Environment $env, FeatureNode $feature, Scenario $scenario, $skip, TestResult $result) { return new SuccessfulTeardown(); } }
  • 49. interface ScenarioTester { public function setUp(Environment $env, FeatureNode $feature, Scenario $scenario, $skip); public function test(Environment $env, FeatureNode $feature, Scenario $scenario, $skip); public function tearDown(Environment $env, FeatureNode $feature, Scenario $scenario, $skip, TestResult $result); }
  • 50. final class EventDispatchingScenarioTester implements ScenarioTester { public function __construct(ScenarioTester $baseTester, EventDispatcherInterface $eventDispatcher) { $this->baseTester = $baseTester; $this->eventDispatcher = $eventDispatcher; } public function setUp(Environment $env, FeatureNode $feature, Scenario $scenario, $skip) { $event = new BeforeScenarioTested($env, $feature, $scenario); $this->eventDispatcher->dispatch($this->beforeEventName, $event); $setup = $this->baseTester->setUp($env, $feature, $scenario, $skip); return $setup; } public function test(Environment $env, FeatureNode $feature, Scenario $scenario, $skip) { return $this->baseTester->test($env, $feature, $scenario, $skip); } public function tearDown(Environment $env, FeatureNode $feature, Scenario $scenario, $skip, TestResult $result) { $teardown = $this->baseTester->tearDown($env, $feature, $scenario, $skip, $result); $event = new AfterScenarioTested($env, $feature, $scenario, $result, $teardown); $this->eventDispatcher->dispatch($event); return $teardown; } }
  • 51. final class HookableScenarioTester implements ScenarioTester { public function __construct(ScenarioTester $baseTester, HookDispatcher $hookDispatcher) { $this->baseTester = $baseTester; $this->hookDispatcher = $hookDispatcher; } public function setUp(Environment $env, FeatureNode $feature, Scenario $example, $skip) { $setup = $this->baseTester->setUp($env, $feature, $scenario, $skip); $hookCallResults = $this->hookDispatcher->dispatchScopeHooks($setup); return new HookedSetup($setup, $hookCallResults); } public function test(Environment $env, FeatureNode $feature, Scenario $scenario, $skip = false) { return $this->baseTester->test($env, $feature, $scenario, $skip); } public function tearDown(Environment $env, FeatureNode $feature, Scenario $scenario, $skip, TestResult $result) { $teardown = $this->baseTester->tearDown($env, $feature, $scenario, $skip, $result); $hookCallResults = $this->hookDispatcher->dispatchScopeHooks($teardown); return new HookedTeardown($teardown, $hookCallResults); } }
  • 53. <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..."> <services> <service id=“tester.scenario” class="BehatBehatTesterScenarioTester” /> <service id=“hooks.tester.scenario” class=“BehatBehatHooksTesterScenarioTester”> ... <tag name=“tester.scenario_wrapper” order=“100”/> </service> <service id=“events.tester.scenario” class=“BehatBehatEventsTesterScenarioTester”> ... <tag name=“tester.scenario_wrapper” order=“200”/> </service> </services> </container>
  • 54. final class ScenarioTesterWrappersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $references = $this->findAndReorderTaggedServices($container, ‘tester.scenario_wrapper’); foreach ($references as $reference) { $id = (string) $reference; $renamedId = $id . '.inner'; // This logic is based on SymfonyComponentDependencyInjectionCompilerDecoratorServicePass $definition = $container->getDefinition(‘tester.scenario’); $container->setDefinition($renamedId, $definition); $container->setAlias('tester.scenario', new Alias($id, $public)); $wrappingService = $container->getDefinition($id); $wrappingService->replaceArgument(0, new Reference($renamedId)); } } ... }
  • 56. behat output Behat has a very simple output:
  • 57. behat output Until you start using backgrounds:
  • 58. behat output And throwing exceptions from their hooks:
  • 59. problem: we need to add behaviour to complex output logic
  • 61. pattern: chain of responsibility
  • 63. final class NodeEventListeningFormatter implements Formatter { public function __construct(EventListener $listener) { $this->listener = $listener; } public static function getSubscribedEvents() { return array(TestworkEventDispatcher::BEFORE_ALL_EVENTS => 'listenEvent'); } public function listenEvent(Event $event, $eventName = null) { $eventName = $eventName ?: $event->getName(); $this->listener->listenEvent($this, $event, $eventName); } }
  • 64. final class ChainEventListener implements EventListener, Countable, IteratorAggregate { private $listeners; public function __construct(array $listeners) { $this->listeners = $listeners; } public function listenEvent(Formatter $formatter, Event $event, $eventName) { foreach ($this->listeners as $listener) { $listener->listenEvent($formatter, $event, $eventName); } } ... }
  • 65. Event listeners Behat has 2 types of listeners: 1. Printers 2. Flow controllers
  • 66. final class StepListener implements EventListener { public function listenEvent(Formatter $formatter, Event $event, $eventName) { $this->captureScenarioOnScenarioEvent($event); $this->forgetScenarioOnAfterEvent($eventName); $this->printStepSetupOnBeforeEvent($formatter, $event); $this->printStepOnAfterEvent($formatter, $event); } ... }
  • 68. class FirstBackgroundFiresFirstListener implements EventListener { public function __construct(EventListener $descendant) { $this->descendant = $descendant; } public function listenEvent(Formatter $formatter, Event $event, $eventName) { $this->flushStatesIfBeginningOfTheFeature($eventName); $this->markFirstBackgroundPrintedAfterBackground($eventName); if ($this->isEventDelayedUntilFirstBackgroundPrinted($event)) { $this->delayedUntilBackgroundEnd[] = array($event, $eventName); return; } $this->descendant->listenEvent($formatter, $event, $eventName); $this->fireDelayedEventsOnAfterBackground($formatter, $eventName); } }
  • 69. where composite and CoR are useful?
  • 71. interface StepTester { public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip); public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip); public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result); }
  • 72. problem: we need to introduce backwards incompatible change into the API
  • 74. interface ScenarioStepTester { public function setUp(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip); public function test(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip); public function tearDown(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip, StepResult $result); }
  • 75. final class StepToScenarioTesterAdapter implements ScenarioStepTester { public function __construct(StepTester $stepTester) { ... } public function setUp(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip) { return $this->stepTester->setUp($env, $feature, $step, $skip); } public function test(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip) { return $this->stepTester->test($env, $feature, $step, $skip); } public function tearDown(Environment $env, FeatureNode $feature, ScenarioNode $scenario, StepNode $step, $skip, StepResult $result) { return $this->stepTester-> tearDown($env, $feature, $step, $skip); } }
  • 76. final class StepTesterAdapterPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $references = $this->processor->findAndSortTaggedServices($container, ‘tester.step_wrapper’); foreach ($references as $reference) { $id = (string) $reference; $renamedId = $id . ‘.adaptee’; $adapteeDefinition = $container->getDefinition($id); $reflection = new ReflectionClass($adapteeDefinition->getClass()); if (!$reflection->implementsInterface(‘StepTester’)) { return; } $container->removeDefinition($id); $container->setDefinition( $id, new Definition(‘StepToScenarioTesterAdapter’, array( $adapteeDefinition )); ); } } }
  • 77. where adapter is useful?
  • 78. demo
  • 80. backwards compatibility Backwards compatibility in Behat comes from the extensibility. 1. Everything is extension 2. New features are extensions too 3. New features could be toggled on/off
  • 82. performance implications · 2x more objects in v3 than in v2 · Value objects are used instead of simple types · A lot of additional concepts throughout · It must be slow
  • 85. how?
  • 89. how?
  • 90. Step1: Close the doors Assume you have no extension points by default. 1. Private properties 2. Final classes
  • 91. Step 2: Open doors properly when you need them 1. Identify the need for extension points 2. Make extension points explicit
  • 94. class BundleFeatureLocator extends FilesystemFeatureLocator { public function locateSpecifications(Suite $suite, $locator) { if (!$suite instanceof SymfonyBundleSuite) { return new noSpecificationsIterator($suite); } $bundle = $suite->getBundle(); if (0 !== strpos($locator, '@' . $bundle->getName())) { return new NoSpecificationsIterator($suite); } $locatorSuffix = substr($locator, strlen($bundle->getName()) + 1); return parent::locateSpecifications($suite, $bundle->getPath() . '/Features' . $locatorSuffix); } }
  • 95. final class BundleFeatureLocator implements SpecificationLocator { public function __construct(SpecificationLocator $baseLocator) { ... } public function locateSpecifications(Suite $suite, $locator) { if (!$suite instanceof SymfonyBundleSuite) { return new noSpecificationsIterator($suite); } $bundle = $suite->getBundle(); if (0 !== strpos($locator, '@' . $bundle->getName())) { return new NoSpecificationsIterator($suite); } $locatorSuffix = substr($locator, strlen($bundle->getName()) + 1); return $this->baseLocator->locateSpecifications($suite, $bundle->getPath() . '/Features' . $locatorSuffix); } }
  • 96. the most closed most extensible testing framework
  • 97. ask questions close Feed! L♻♻ps: https://joind.in/11559