SlideShare una empresa de Scribd logo
1 de 97
Descargar para leer sin conexión
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
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
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?
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...
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

Más contenido relacionado

La actualidad más candente

Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 

La actualidad más candente (20)

Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Frontin like-a-backer
Frontin like-a-backerFrontin like-a-backer
Frontin like-a-backer
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 

Destacado

Destacado (20)

Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Design pattern in Symfony2 - Nanos gigantium humeris insidentesDesign pattern in Symfony2 - Nanos gigantium humeris insidentes
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
 
Design patterns avec Symfony
Design patterns avec SymfonyDesign patterns avec Symfony
Design patterns avec Symfony
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
Modern Agile Project Toolbox
Modern Agile Project ToolboxModern Agile Project Toolbox
Modern Agile Project Toolbox
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)
 
Taking back BDD
Taking back BDDTaking back BDD
Taking back BDD
 
Understanding craftsmanship
Understanding craftsmanshipUnderstanding craftsmanship
Understanding craftsmanship
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
BDD by example
BDD by exampleBDD by example
BDD by example
 
Modern Project Toolbox
Modern Project ToolboxModern Project Toolbox
Modern Project Toolbox
 
Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projects
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и MinkBDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
 
BDD для PHP проектов
BDD для PHP проектовBDD для PHP проектов
BDD для PHP проектов
 
Data Flow Patterns in Angular 2 - Sebastian Müller
Data Flow Patterns in Angular 2 -  Sebastian MüllerData Flow Patterns in Angular 2 -  Sebastian Müller
Data Flow Patterns in Angular 2 - Sebastian Müller
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
 
Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
Symfony2: 30 astuces et bonnes pratiques
Symfony2: 30 astuces et bonnes pratiquesSymfony2: 30 astuces et bonnes pratiques
Symfony2: 30 astuces et bonnes pratiques
 

Similar a Decoupling with Design Patterns and Symfony2 DIC

Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
IndicThreads
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
vhazrati
 

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

PHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodePHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better Code
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmers
 
Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2
 
Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...
 
Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2
 
A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014
A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014
A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 
A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014
 
Overview Of Lift Framework
Overview Of Lift FrameworkOverview Of Lift Framework
Overview Of Lift Framework
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 

Último

AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 

Último (20)

AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
Generic or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisionsGeneric or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisions
 
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 

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
  • 3.
  • 4.
  • 5.
  • 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
  • 10.
  • 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?
  • 70.
  • 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
  • 84.
  • 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