SlideShare una empresa de Scribd logo
1 de 84
Descargar para leer sin conexión
deSymfonyDay
Barcelona 2014
DIC To The
Limit
Ronny López
ABOUT ME US
Hard way learner	

!
Technical Lead at Social Point	

Do stuff atTangoTree in the nights	

!
@ronnylt

www.tangotree.io

https://github.com/ronnylt
AGENDA
• The problems	

• Dependency Injection	

• Coupling done right	

• Dependency Inversion Principle	

• Types of DI	

• Dependency Injection Containers	

• Symfony Dependency Injection Container	

• Interchangeable Services
THE PROBLEMS
• We require to switch out data access layer without
compromising all the other parts of the application	

• We need to use different implementations in different
deployments (dragon game, monster game, etc…)	

• We wish to deploy the application in different environments
(testing, integration, staging, production, etc…)	

• We need different configurations in each environment
YAGNI	

You aren't gonna need it, let’s go home!
IN CASEYOU NEED IT
• You have to do Dependency Injection, correctly	

• You have to know and apply Dependency Inversion
Principles
WHAT WE REALLY WANT IS
• Code that is easy to test	

• Clear separation of infrastructure logic from
application logic	

• Interchangeable infrastructure
WHY?
• Multiple deployments share parts of the same code,
but each deployment has specific infrastructure
needs	

• Multiple environments with different needs	

• We want to automatically test all the things	

• We want to go fast, and the only way to go fast is…
you know…
“The only way to
go fast, is to go
well”

Uncle Bob
DEPENDENCY 	

INJECTION
Dependency injection is a
simple way to decouple
classes from what they
depend on
Dependency injection
decouples classes
construction from the
construction of it’s
dependencies
IS IT ALL ABOUT
COUPLING?
COUPLING
Mod A Mod B
Mod C Mod D
Tight (high, strong)
Mod A Mod B
Mod C Mod D
Loose (low, weak)
Mod A Mod B
Mod C Mod D
None
Coupling is a measure of the independency of
components/modules
COUPLING
• The history of software shows that coupling is bad,
but it also suggest that coupling is unavoidable 	

• An absolutely decoupled application is useless because
it adds no value	

• Developers can only add value by coupling things
together
COUPLING DONE RIGHT
• Components make no assumptions about what other
components do, but rely on their contracts	

• Use an interface to define a type and focus on what
is important	

• Concrete implementations of the interfaces should be
be instantiated outside the component
DEPENDENCY INVERSION
PRINCIPLE
High-level modules should
not depend on low-level
modules. 	

Both should depend on
abstractions
Abstractions should not
depend on details. 	

Details should depend on
abstractions
DEPENDENCY INVERSION
• Decouple high level parts of the system from low level
parts by using interfaces
OrderProcessor OrderRepository
MySqlOrderRepository
CassandraOrderRepository
LockSystem
RedisLockSystem
ZooKeeperLockSystem
<<interface>>
<<interface>>
ObjectStore RedisStorageDriver
InMemoryStorageDriver
RiakStorageDriver
StorageDriver
<<interface>>
TYPES OF DEPENDENCY
INJECTIONS	

!
TYPES OF DI
• Constructor Injection	

• Setter Injection	

• Interface Injection	

• Property Injection	

• Service Locator
CONSTRUCTOR INJECTION
• Injects the dependencies via constructor	

• It ensures that the choice of dependency is immutable
!
class EnergyBuyCommandHandler implements CommandHandler!
{!
private $playerRepository;!
!
private $configRepository;!
!
public function __construct(!
! ! PlayerRepository $playerRepository, !
! ! ConfigRepository $config!
! )!
{!
$this->playerRepository = $playerRepository;!
$this->configRepository = $config;!
}!
}!
CONSTRUCTOR INJECTION
• It ensures that the choice of
dependency is immutable	

• The constructor is only ever
called once when the object
is created, so you can be sure
that the dependency will not
change during the object's
lifetime
Pros Cons
• It is not suitable for working
with optional dependencies	

• Difficult to use in combination
with class hierarchies
CONSTRUCTOR INJECTION
• Try to always use constructor injection	

• If dealing with legacy code that does not support it,
consider using an adapter class with constructor
injection	

• Depends on abstractions (interfaces), not
concrete implementations
TIPS
SETTER INJECTION
• Inject the dependencies via a setter method	

• The “injector” has to call the method in order to inject
the dependency
!
class LoggerChain implements SQLLogger!
{!
private $loggers = array();!
!
public function setLogger(SQLLogger $logger)!
{!
$this->logger = $logger;!
}!
}!
SETTER INJECTION
• Works “well” with optional
dependencies



If you do not need the
dependency, then just do not
call the setter	

• You can call the setter multiple
times.



This is particularly useful if the
method adds the dependency
to a collection
Pros Cons
• Works “well” with optional
dependencies 



Are you sure you need optional
dependencies?	

• You can call the setter multiple
times	

• You are not sure if the dependency
was set
SETTER INJECTION
TIPS
• Avoid setter injections (the choice of dependencies
is not inmutable)	

• If you do Dependency Inversion right, probably
YANGI	

• Remember, your classes depend on abstractions, not
concrete implementations, so you can use Null or
Dummy implementations when necessary
SETTER INJECTION
EXAMPLE
use PsrLogLoggerInterface;!
!
final class OrderProcessor!
{!
private $logger;!
!
function __construct(. . ., LoggerInterface $logger)!
{!
$this->logger = $logger;!
}!
}!
!
final class GoodManLogger implements LoggerInterface {…}!
!
final class LogstarLogger implements LoggerInterface {…}!
!
final class NullLogger implements LoggerInterface {…}!
Instead of having a setter method to inject
the logger, use constructor injection and use
the appropriate logger implementation in
each case
INTERFACE INJECTION
• Define and use interfaces for the injection	

• Allows certain objects to be injected into other objects, that
implement a common interface	

• It’s a kind of setter injection, so same pros and cons	

interface ContainerAwareInterface!
{!
public function setContainer(ContainerInterface $container = null);!
}!
!
class ContainerAwareEventDispatcher implements ContainerAwareInterface!
{!
public function setContainer(ContainerInterface $container = null)!
{!
}!
}!
PROPERTY INJECTION
• Allows setting public fields of the class directly	

• There are mainly only disadvantages to using property
injection, it is similar to setter injection but with additional
important problems
!
!
class NewsletterManager!
{!
public $mailer;!
!
// ...!
}!
PROPERTY INJECTION
• Useful if you are working with
code that is out of your control,
such as in a 3rd party library,
which uses public properties for
its dependencies
Pros Cons
• You cannot control when the
dependency is set at all, it can be
changed at any point in the object's
lifetime	

• You cannot use type hinting so you
cannot be sure what dependency is
injected except by writing into the
class code to explicitly test the class
instance before using it
SERVICES LOCATOR
• Is an object that knows how to get all of the services that an
another service might need
interface CommandHandlerLocator !
{!
public function locate($commandName);!
}!
!
!
class ContainerCommandHandlerLocator implements CommandHandlerLocator!
{!
private $container;!
!
public function __construct(ContainerInterface $container)!
{!
$this->container = $container;!
}!
!
public function locate($commandName)!
{!
if (!$this->container->has($commandName)) {!
throw new NotFoundException('Unable to find command handler');!
}!
!
return $this->container->get($commandName);!
}!
}!
SERVICE LOCATOR
• It’s easy to use and abuse due
to its straightforward behaviour	

• Not all use cases are bad, for
example when you want to
load services on demand at
runtime
Pros Cons
• It hides dependencies in your code
making them difficult to figure out
and potentially leads to errors that
only manifest themselves at runtime	

• It becomes unclear what are the
dependencies of a given class	

• It’s easy to abuse	

• It’s consider and anti-pattern (but
there are valid use cases for it)
SERVICE LOCATOR
TIPS
• Use a segregated interface for the locator, do not
depends on the whole locator (container)	

!
• Limit the types of services a locator provides
DEPENDENCY INJECTION 	

CONTAINER
Dependency Injection
Container is simply an
object that manages the
instantiation of other objects
Automagically creates a
given type with all the
required dependencies
DIC
• Most of the time you do not need a DIC to benefit from
Dependency Injection	

• But… creating and maintaining the dependencies by hand
can become a nightmare pretty fast	

• A DIC manages objects from their instantiation to their
configuration	

• The objects themselves should not know that they are
managed by a container and should not know nothing
about it
DEPENDENCY INJECTION 	

CONTAINERS IN PHP
PHP DIC IMPLEMENTATIONS
• Twittee 	

• Pimple	

• IlluminateDi	

• ZendDi	

• SymfonyDependencyInjection	

• http://php-di.org/	

• Build your own
SYMFONY 

DEPENDENCY
INJECTION CONTAINER
The Dependency Injection
component allows you to
standardize and centralize
the way objects are
constructed in a SF
application
HOW IT WORKS?
• Reads definition of how objects (services) should be
constructed (XML,YAML, PHP, etc…)	

• Collects all definitions and builds a container	

• When requested, creates objects and injects the
dependencies
ADVANCED
HOW IT WORKS
• Compiler Passes	

• Container Extensions	

• Services Configurator	

• Tagged Services
SYMFONY DIC
• Basic (and advances) usages are well documented in
the Symfony official documentation	

• Probably you are comfortable creating your own
objects via the container	

• So, let’s try to solve the problems we stated at the
beginning
INTERCHANGEABLE
SERVICES
OrderProcessor OrderRepository
MySqlOrderRepository
CassandraOrderRepository
LockSystem
RedisLockSystem
ZooKeeperLockSystem
<<interface>>
<<interface>>
ObjectStore RedisStorageDriver
InMemoryStorageDriver
RiakStorageDriver
StorageDriver
<<interface>>
final class OrderProcessor!
{!
private $orderRepository;!
private $lockSystem;!
private $objectStore;!
!
function __construct(!
! ! OrderRepository $repository, !
! ! LockSystem $lockSystem, !
! ! ObjectStore $objectStore)!
{!
$this->orderRepository = $repository;!
$this->lockSystem = $lockSystem;!
$this->objectStore = $objectStore;!
}!
}!
!
final class ObjectStore!
{!
function __construct(StorageDriver $driver)!
{!
}!
}!
!
!
interface StorageDriver !
{}!
!
final class RiakStorageDriver implements StorageDriver!
{}!
!
final class RedisStorageDriver implements StorageDriver!
{}!
!
final class InMemoryStorageDriver implements StorageDriver!
{}!
!
interface LockSystem !
{}!
!
final class RedisLockSystem implements LockSystem!
{}!
!
final class ZooKeeperLockSystem implements LockSystem!
{}!
interface OrderRepository {}!
!
final class MySqlOrderRepository implements OrderRepository!
{}!
!
final class CassandralOrderRepository implements OrderRepository!
{}!
services:!
! order_processor:!
! ! class: OrderProcessor!
arguments:!
! ! ! object_store: @object_store!
! ! ! order_repository: [mysql|cassandra] !
! ! ! lock_system: [redis|zookeeper]!
! ! ! !
!
! object_store:!
! ! class: ObjectStore!
! ! arguments:!
! ! storage_driver: [riak|redis|inmemory]!
MySqlOrderRepository
CassandraOrderRepository
RedisLockSystem
ZooKeeperLockSystem
RedisStorageDriver
InMemoryStorageDriver
RiakStorageDriver
THE PROBLEM
• It’s not possible to decide in advance what concrete
implementation a deployment or environment is going
to use	

• We must configure dependencies for all the concrete
implementations and each one has their own
dependencies
SERVICES SETUP
Deployment Environment Concrete Implementation
DRAGON TESTING in-memory
DRAGON STAGING/QA redis
DRAGON PROD mysql
MONSTER TESTING in-memory
MONSTER PROD cassandra
SOLUTIONS
1. Loading different configurations depending on the
environment and aliasing services	

2. Using synthetic services and aliases	

3. Managing semantic configuration with extensions	

4. Using a custom injector
1- LOADING DIFFERENT CONFIGURATIONS
Load different configurations depending on the
kernel.environment
• Probably easy to setup for
testing/dummy services
Pros Cons
• The choice is coupled to the
environment	

• All services get defined, even when
you are not going to use them
!
namespace AcmeDemoBundleDependencyInjection;!
!
use SymfonyComponentDependencyInjectionContainerBuilder;!
use SymfonyComponentDependencyInjectionLoaderXmlFileLoader;!
use SymfonyComponentHttpKernelDependencyInjectionExtension;!
use SymfonyComponentConfigFileLocator;!
!
class AcmeDemoExtension extends Extension!
{!
public function load(array $configs, ContainerBuilder $container)!
{!
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));!
$loader->load('services.yml');!
!
if ($this->container->getParameter('kernel.environment') == 'test') {!
$loader->load('services_test.yml');!
}!
}!
!
public function getAlias()!
{!
return 'acme_demo';!
}!
}!
services:!
storage_driver:!
alias: storage_driver.riak!
!
storage_driver.riak:!
class: RiakStorageDriver!
!
storage_driver.redis:!
class: RedisStorageDriver!
!
storage_driver.memory:!
class: InMemoryStorageDriver
!
services:!
storage_driver:!
alias: storage_driver.memory!
services.yml services_test.yml
The choice is coupled to
the environment
2- USING SYNTHETIC SERVICES AND ALIAS
Define abstraction as “synthetic” services
• Probably easy to setup for
testing/dummy services
Pros Cons
• All services get defined, even when
you are not going to use them	

• You have to define dependencies of
services you probably not are going
to use
!
namespace AcmeDemoBundleDependencyInjection;!
!
use SymfonyComponentDependencyInjectionContainerBuilder;!
use SymfonyComponentDependencyInjectionLoaderXmlFileLoader;!
use SymfonyComponentHttpKernelDependencyInjectionExtension;!
use SymfonyComponentConfigFileLocator;!
!
class AcmeDemoExtension extends Extension!
{!
public function load(array $configs, ContainerBuilder $container)!
{!
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));!
$loader->load('services.yml');!
}!
!
public function getAlias()!
{!
return 'acme_demo';!
}!
}!
services:!
storage_driver:!
synthetic: true!
!
storage_driver.riak:!
class: RiakStorageDriver!
!
storage_driver.redis:!
class: RedisStorageDriver!
!
storage_driver.memory:!
class: InMemoryStorageDriver!
!
services:!
storage_driver:!
alias: storage_driver.redis!
services.yml app/config/config.yml
!
services:!
storage_driver:!
alias: storage_driver.memory!
app/config/config_test.yml
3- MANAGING SEMANTIC CONFIGURATIONS
WITH EXTENSIONS
• Instead of having the user override individual
parameters, you let the user configure just a few,
specifically created options.	

• As the bundle developer, you then parse through that
configuration and load services inside an “Extension"	

• With this method, you won't need to import any
configuration resources from your main application
configuration: the Extension class can handle all of this
3- MANAGING SEMANTIC CONFIGURATIONS
WITH EXTENSIONS
• Good fit if you are building a bundle to be used by 3rd
parties and you have a lot of configuration options you
want to validate	

• Lot of boilerplate code	

• Extra complexity added	

• You just wanted to manage dependency injection right?
class Configuration implements ConfigurationInterface!
{!
protected function addObjectStoreSection(ArrayNodeDefinition $rootNode)!
{!
$rootNode!
->children()!
->arrayNode('object_store')!
->isRequired()!
->children()!
->arrayNode('store_driver')!
->children()!
->scalarNode('type')->isRequired()->end()!
->scalarNode('connection')->end()!
->scalarNode('create_buckets')->end()!
->end()!
->validate()!
->ifTrue(function ($v) {!
switch ($v['type']) {!
case 'doctrine_dbal':!
if (!isset($v['connection']) ||!
!isset($v['create_buckets'])) {!
return true;!
}!
break;!
}!
!
return false;!
})!
->thenInvalid('children configuration')!
->end()!
->isRequired()!
->end()!
->end()!
->end();!
}!
}
!
$definition = $container->getDefinition($storageDriver);!
switch ($config['storage_driver']['type']) {!
case 'doctrine_dbal':!
$definition->replaceArgument(0, new Reference($config['storage_driver']['connection']));!
$definition->replaceArgument(1, $config['storage_driver']['create_buckets']);!
break;!
case 'redis':!
$definition->replaceArgument(0, new Reference($config['storage_driver']['connection']));!
break;!
}!
!
$container->setAlias('object_storage.driver', $storageDriver);!
Configuration
Extension
3- MANAGING SEMANTIC CONFIGURATIONS
WITH EXTENSIONS
• More powerful than simply
defining parameters: a specific
option value might trigger the
creation of many service
definitions;	

• Ability to have configuration
hierarchy	

• Smart merging of several config
files (e.g. config_dev.yml and
config.yml)
Pros Cons
• Too much verbose, a lot of
boilerplate code	

• Extra complexity added if you only
want to define dependencies and
not a long configuration tree	

!
4- USING A CUSTOM INJECTOR
• Let’s forget the Symfony DIC for a moment	

• Let’s go back to dependency management problem
again
services:!
! order_processor:!
! ! class: OrderProcessor!
arguments:!
! ! ! object_store: @object_store!
! ! ! order_repository: [mysql|cassandra] !
! ! ! lock_system: [redis|zookeeper]!
! ! ! !
!
! object_store:!
! ! class: ObjectStore!
! ! arguments:!
! ! storage_driver: [riak|redis|inmemory]!
MySqlOrderRepository
CassandraOrderRepository
RedisLockSystem
ZooKeeperLockSystem
RedisStorageDriver
InMemoryStorageDriver
RiakStorageDriver
“A good architecture allows
you to defer critical decisions,
it doesn’t force you to defer
them. 	

However, if you can defer
them, it means you have lots
of flexibility”	

!
Uncle Bob
“A good architecture allows
volatile decisions to be easily
changed”	

!
Uncle Bob
services:!
! order_processor:!
! ! class: OrderProcessor!
arguments:!
! ! ! object_store: @object_store!
! ! ! order_repository: [mysql|cassandra] !
! ! ! lock_system: [redis|zookeeper]!
! ! ! !
!
! object_store:!
! ! class: ObjectStore!
! ! arguments:!
! ! storage_driver: [riak|redis|inmemory]!
MySqlOrderRepository
CassandraOrderRepository
RedisLockSystem
ZooKeeperLockSystem
RedisStorageDriver
InMemoryStorageDriver
RiakStorageDriver
We need a dependency injection
tool that allow us to easily change
volatile decisions
We need a dependency injection
tool that allow us to defer critical
decisions
Core Framework
Payment Component
Analytics Component
Object Store Lock System
Component Exports Depends On
Unit Of Work Storage Driver
Order Processor Product Repository
Order Repository
Payment Gateway
Tracker Tracker Queue
Implementations
Redis, Zookeper
Redis, C*, Riak,
MySql
Config, API
MySql, C*
Itunes, Facebook,
Amazon, Google
Play
Redis, RabbitMQ,
SQS, Kinesis
Core Component Object Store Lock System
Component Exports Depends On
Unit Of Work Storage Driver
Implementations
Redis, Zookeper
Redis, C*, Riak,
MySql
namespace SPCoreGameFramework;!
!
class FrameworkComponent implements Component!
{!
public function getName()!
{!
return 'core.game.framework';!
}!
!
public function getServicesDefinition()!
{!
return ServicesDefinition::create()!
!
->dependsOn('storage_driver')!
->withInstanceOf('SPCoreComponentObjectStoreStorageStorageDriver')!
!
->dependsOn('lock_system')!
->withInstanceOf('SPCoreComponentLockLockSystem')!
!
->exports('object_store')!
->withClass('SPCoreComponentObjectStore')!
->andConstructorDependencies(‘storage_driver’, ‘lock_system’);!
}!
}
These are the decisions the
component developer
wants to defer
Depends on abstractions,
not concrete
implementations
Payment Component Order Processor Product Repository
Order Repository
Config, API
MySql, C*
namespace SPCoreGamePayment;!
!
class PaymentComponent implements Component!
{!
public function getName()!
{!
return 'core.game.payment';!
}!
!
public function getServicesDefinition()!
{!
return ServicesDefinition::create()!
!
! ! ! ->dependsOn('product_repository')!
->withInstanceOf('SPCoreGamePaymentProductRepositoryProductRepository')!
!
->dependsOn('order_repository')!
->withInstanceOf('SPCoreGamePaymentOrderRepositoryOrderRepository')!
!
->dependsOn('gateways')!
->withInstanceOf('GatewayDefinition')!
!
->exports(‘order_processor')!
->withClass('SPCoreGamePaymentOrderProcessor')!
->andConstructorDependencies(‘gateways', ‘product_repository’, ‘order_repository’);!
}!
}
Itunes, Facebook,
Amazon, Google
Play
Payment Gateway
These are the decisions the
component developer
wants to defer
Depends on abstractions,
not concrete
implementations
WHERE DOESTHE MAGIC COME FROM?
• There is not such “magic”	

• Each component define it’s dependencies in a easy and
legible way	

• Framework agnostic dependency definition	

• Based on the dependency definitions, services are
added to the container during the container building
phase
HOW?
• First, collect services definitions from installed
components	

• Second, inject services definitions into the Symfony (or
Silex, or…) container	

• No magic, just code !!!
USING SYMFONY
• Collect services definitions from installed components	

• Inject services definitions into the Symfony (or Silex,
or…) container
use SymfonyComponentDependencyInjection as DI;!
!
!
final class ComponentDependencyInjector implements DICompilerCompilerPassInterface!
{!
private $components;!
!
public function __construct(array $components = array())!
{!
$this->components = $components;!
}!
!
public function process(ContainerBuilder $container)!
{!
foreach ($this->components as $component) {!
$services = $component->getServicesDefinition();!
foreach ($services->dependencies as $definition) {!
$id = $component->getName() . '.' . $definition->name;!
!
if (!$container->has($id)) {!
throw new ServiceNotFoundException($component->getName(), $id, $definition->instanceOf);!
}!
}!
}!
}!
!
public function registerComponentsDependencies(ContainerBuilder $container)!
{!
foreach ($this->components as $component) {!
$this->addComponentDependencies($container, $component);!
}!
}!
!
private function addComponentDependencies(ContainerBuilder $container, Component $component)!
{!
$container->addObjectResource($component);!
$services = $component->getServicesDefinition();!
foreach ($services->exports as $definition) {!
$this->addDefinition($container, $definition, $component);!
}!
foreach ($services->definitions as $definition) {!
$this->addDefinition($container, $definition, $component);!
}!
}!
!
$def = new DIDefinition($definition->class, $args);!
$def->setPublic($definition->public);!
!
$container->setDefinition($component->getName() . '.' . $definition->name, $def);!
}!
}!
The Component Dependency
Injector responsibility is to inject
the definitions in a given container
use SymfonyComponentHttpKernelKernel;!
use SymfonyComponentConfigLoaderLoaderInterface;!
!
class AppKernel extends Kernel!
{!
public function registerBundles()!
{!
$bundles = array(!
new SymfonyBundleFrameworkBundleFrameworkBundle(),!
new SymfonyBundleMonologBundleMonologBundle(),!
!
new SPCoreBundleGameFrameworkBundleGameFrameworkBundle([!
new SPCoreGameFrameworkFrameworkComponent(),!
new SPCoreGameFrameworkPaymentComponent(),!
new SPCoreGameFrameworkAnalyticsComponent(),!
]),!
);!
}!
}!
The AppKernel
These are the framework agnostic components that
provide infrastructure and logic for the application
namespace SPCoreBundleGameFrameworkBundle;!
!
use SymfonyComponentDependencyInjectionCompilerPassConfig;!
use SymfonyComponentHttpKernelBundleBundle;!
use SymfonyComponentDependencyInjectionContainerBuilder;!
!
use SPCoreBridgeSymfonyDependencyInjectionComponentDependencyInjector;!
!
class GameFrameworkBundle extends Bundle!
{!
private $components;!
!
function __construct(array $components = array())!
{!
$this->components = $components;!
}!
!
public function build(ContainerBuilder $container)!
{!
$injector = new ComponentDependencyInjector($this->components);!
$injector->registerComponentsDependencies($container);!
!
$container->addCompilerPass($injector, PassConfig::TYPE_BEFORE_REMOVING);!
}!
}!
The (in)Famous Framework Bundle
The components we are “installing” in this
application
The “magic” is here !!!
[SPCoreBridgeSymfonyDependencyInjectionExceptionServiceNotFoundException]
Component "core.game.framework" requires the service
"core.game.framework.lock_system" as an implementation of "SPCoreComponentLock
LockSystem" but the service is not defined.
[SPCoreBridgeSymfonyDependencyInjectionExceptionServiceNotFoundException]
Component "core.game.payment" requires the service
“core.game.payment.product_repository” as an implementation of "ProductRepository" but
the service is not defined.
$ php app/console
The application complains about
missing dependencies required
by installed components
We are planning to add
suggested implementations for
this requirement
services:!
!
! core.game.framework.lock_system:!
! ! public: false!
! ! class: SPCoreComponentLockRedisLockSystem!
! ! arguments:!
redis: @sp.core.redis.connector.lock!
timeout: 10!
expiration: 10!
config.yml
services:!
!
! core.game.framework.lock_system:!
! ! public: false!
! ! class: SPCoreComponentLockMemoryLockSystem!
! ! arguments:!
timeout: 10!
expiration: 10!
config_test.yml
The application config Concrete
implementation in the
application (by default)
Semantic configuration?
Concrete
implementation in the
application (test mode)
4- USING A CUSTOM INJECTOR
• Complete control of
component dependencies	

• Very limited in features (only
constructor injections allowed)	

• Framework agnostic	

• Allows you to defer critical and
volatile decisions
Pros Cons
• Very limited in features (only
constructor injections allowed)	

!
!
CONCLUSIONS
• You have the tools, use the best tool that solve your
problems	

• Don’t be afraid of going to the limit with the
framework you use	

• The framework is just an implementation detail
SOME REFERENCES
http://www.objectmentor.com/resources/articles/dip.pdf
!
http://www.martinfowler.com/articles/injection.html
!
http://martinfowler.com/articles/dipInTheWild.html
!
http://desymfony.com/ponencia/2013/inyeccion-dependencias-aplicaciones-php
!
http://fabien.potencier.org/article/11/what-is-dependency-injection
!
http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container
!
http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container
!
https://www.youtube.com/watch?v=IKD2-MAkXyQ
!
http://richardmiller.co.uk/2014/03/12/avoiding-setter-injection/
!
http://richardmiller.co.uk/2014/03/28/symfony2-configuring-different-services-for-different-environments/
!
http://www.infoq.com/news/2013/07/architecture_intent_frameworks
!
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
!
THANK YOU
For your attention
@ronnylt

Más contenido relacionado

La actualidad más candente

La actualidad más candente (15)

Refactor your way forward
Refactor your way forwardRefactor your way forward
Refactor your way forward
 
Intro To AOP
Intro To AOPIntro To AOP
Intro To AOP
 
AngularConf2015
AngularConf2015AngularConf2015
AngularConf2015
 
Introduction To AOP
Introduction To AOPIntroduction To AOP
Introduction To AOP
 
Dark Side of iOS [SmartDevCon 2013]
Dark Side of iOS [SmartDevCon 2013]Dark Side of iOS [SmartDevCon 2013]
Dark Side of iOS [SmartDevCon 2013]
 
Learn How to Unit Test Your Android Application (with Robolectric)
Learn How to Unit Test Your Android Application (with Robolectric)Learn How to Unit Test Your Android Application (with Robolectric)
Learn How to Unit Test Your Android Application (with Robolectric)
 
iOS Application Exploitation
iOS Application ExploitationiOS Application Exploitation
iOS Application Exploitation
 
Singleton Object Management
Singleton Object ManagementSingleton Object Management
Singleton Object Management
 
Android Malware and Machine Learning
Android Malware and Machine LearningAndroid Malware and Machine Learning
Android Malware and Machine Learning
 
Using TypeScript with Angular
Using TypeScript with AngularUsing TypeScript with Angular
Using TypeScript with Angular
 
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 03)
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 03) iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 03)
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 03)
 
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 02)
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 02) iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 02)
iOS Bootcamp: learning to create awesome apps on iOS using Swift (Lecture 02)
 
Unit testing and Android
Unit testing and AndroidUnit testing and Android
Unit testing and Android
 
Android Deobfuscation: Tools and Techniques
Android Deobfuscation: Tools and TechniquesAndroid Deobfuscation: Tools and Techniques
Android Deobfuscation: Tools and Techniques
 
Tech breakfast 18
Tech breakfast 18Tech breakfast 18
Tech breakfast 18
 

Similar a DIC To The Limit – deSymfonyDay, Barcelona 2014

Mock Objects, Design and Dependency Inversion Principle
Mock Objects, Design and Dependency Inversion PrincipleMock Objects, Design and Dependency Inversion Principle
Mock Objects, Design and Dependency Inversion Principle
P Heinonen
 
Orthogonality: A Strategy for Reusable Code
Orthogonality: A Strategy for Reusable CodeOrthogonality: A Strategy for Reusable Code
Orthogonality: A Strategy for Reusable Code
rsebbe
 
Build software like a bag of marbles, not a castle of LEGO®
Build software like a bag of marbles, not a castle of LEGO®Build software like a bag of marbles, not a castle of LEGO®
Build software like a bag of marbles, not a castle of LEGO®
Hannes Lowette
 
Architecting, testing and developing an mvc application
Architecting, testing and developing an mvc applicationArchitecting, testing and developing an mvc application
Architecting, testing and developing an mvc application
Maxime Rouiller
 
Writing Testable Code
Writing Testable CodeWriting Testable Code
Writing Testable Code
jameshalsall
 

Similar a DIC To The Limit – deSymfonyDay, Barcelona 2014 (20)

Mock Objects, Design and Dependency Inversion Principle
Mock Objects, Design and Dependency Inversion PrincipleMock Objects, Design and Dependency Inversion Principle
Mock Objects, Design and Dependency Inversion Principle
 
Orthogonality: A Strategy for Reusable Code
Orthogonality: A Strategy for Reusable CodeOrthogonality: A Strategy for Reusable Code
Orthogonality: A Strategy for Reusable Code
 
Introduction to Dependency Injection
Introduction to Dependency InjectionIntroduction to Dependency Injection
Introduction to Dependency Injection
 
Eurosport's Kodakademi #2
Eurosport's Kodakademi #2Eurosport's Kodakademi #2
Eurosport's Kodakademi #2
 
Build software like a bag of marbles, not a castle of LEGO®
Build software like a bag of marbles, not a castle of LEGO®Build software like a bag of marbles, not a castle of LEGO®
Build software like a bag of marbles, not a castle of LEGO®
 
Zend Di in ZF 2.0
Zend Di in ZF 2.0Zend Di in ZF 2.0
Zend Di in ZF 2.0
 
Android testing
Android testingAndroid testing
Android testing
 
Architecting, testing and developing an mvc application
Architecting, testing and developing an mvc applicationArchitecting, testing and developing an mvc application
Architecting, testing and developing an mvc application
 
Writing Testable Code
Writing Testable CodeWriting Testable Code
Writing Testable Code
 
Breaking Dependencies to Allow Unit Testing - DevIntersection Spring 2016
Breaking Dependencies to Allow Unit Testing - DevIntersection Spring 2016Breaking Dependencies to Allow Unit Testing - DevIntersection Spring 2016
Breaking Dependencies to Allow Unit Testing - DevIntersection Spring 2016
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
Mobile App Architectures & Coding guidelines
Mobile App Architectures & Coding guidelinesMobile App Architectures & Coding guidelines
Mobile App Architectures & Coding guidelines
 
The Architect Way
The Architect WayThe Architect Way
The Architect Way
 
Patterns and practices for building enterprise-scale HTML5 apps
Patterns and practices for building enterprise-scale HTML5 appsPatterns and practices for building enterprise-scale HTML5 apps
Patterns and practices for building enterprise-scale HTML5 apps
 
Getting started with Xamarin forms
Getting started with Xamarin formsGetting started with Xamarin forms
Getting started with Xamarin forms
 
Clean code
Clean codeClean code
Clean code
 
Onion Architecture / Clean Architecture
Onion Architecture / Clean ArchitectureOnion Architecture / Clean Architecture
Onion Architecture / Clean Architecture
 
springtraning-7024840-phpapp01.pdf
springtraning-7024840-phpapp01.pdfspringtraning-7024840-phpapp01.pdf
springtraning-7024840-phpapp01.pdf
 
Weekly Meeting: Basic Design Pattern
Weekly Meeting: Basic Design PatternWeekly Meeting: Basic Design Pattern
Weekly Meeting: Basic Design Pattern
 
Cut your Dependencies with Dependency Injection - .NET User Group Osnabrueck
Cut your Dependencies with Dependency Injection - .NET User Group OsnabrueckCut your Dependencies with Dependency Injection - .NET User Group Osnabrueck
Cut your Dependencies with Dependency Injection - .NET User Group Osnabrueck
 

Último

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
giselly40
 

Último (20)

ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 

DIC To The Limit – deSymfonyDay, Barcelona 2014

  • 2.
  • 3. Ronny López ABOUT ME US Hard way learner ! Technical Lead at Social Point Do stuff atTangoTree in the nights ! @ronnylt
 www.tangotree.io
 https://github.com/ronnylt
  • 4. AGENDA • The problems • Dependency Injection • Coupling done right • Dependency Inversion Principle • Types of DI • Dependency Injection Containers • Symfony Dependency Injection Container • Interchangeable Services
  • 5. THE PROBLEMS • We require to switch out data access layer without compromising all the other parts of the application • We need to use different implementations in different deployments (dragon game, monster game, etc…) • We wish to deploy the application in different environments (testing, integration, staging, production, etc…) • We need different configurations in each environment
  • 6. YAGNI You aren't gonna need it, let’s go home!
  • 7. IN CASEYOU NEED IT • You have to do Dependency Injection, correctly • You have to know and apply Dependency Inversion Principles
  • 8. WHAT WE REALLY WANT IS • Code that is easy to test • Clear separation of infrastructure logic from application logic • Interchangeable infrastructure
  • 9. WHY? • Multiple deployments share parts of the same code, but each deployment has specific infrastructure needs • Multiple environments with different needs • We want to automatically test all the things • We want to go fast, and the only way to go fast is… you know…
  • 10.
  • 11. “The only way to go fast, is to go well”
 Uncle Bob
  • 13. Dependency injection is a simple way to decouple classes from what they depend on
  • 14. Dependency injection decouples classes construction from the construction of it’s dependencies
  • 15. IS IT ALL ABOUT COUPLING?
  • 16. COUPLING Mod A Mod B Mod C Mod D Tight (high, strong) Mod A Mod B Mod C Mod D Loose (low, weak) Mod A Mod B Mod C Mod D None Coupling is a measure of the independency of components/modules
  • 17. COUPLING • The history of software shows that coupling is bad, but it also suggest that coupling is unavoidable • An absolutely decoupled application is useless because it adds no value • Developers can only add value by coupling things together
  • 18. COUPLING DONE RIGHT • Components make no assumptions about what other components do, but rely on their contracts • Use an interface to define a type and focus on what is important • Concrete implementations of the interfaces should be be instantiated outside the component
  • 20. High-level modules should not depend on low-level modules. Both should depend on abstractions
  • 21. Abstractions should not depend on details. Details should depend on abstractions
  • 22. DEPENDENCY INVERSION • Decouple high level parts of the system from low level parts by using interfaces
  • 25. TYPES OF DI • Constructor Injection • Setter Injection • Interface Injection • Property Injection • Service Locator
  • 26. CONSTRUCTOR INJECTION • Injects the dependencies via constructor • It ensures that the choice of dependency is immutable ! class EnergyBuyCommandHandler implements CommandHandler! {! private $playerRepository;! ! private $configRepository;! ! public function __construct(! ! ! PlayerRepository $playerRepository, ! ! ! ConfigRepository $config! ! )! {! $this->playerRepository = $playerRepository;! $this->configRepository = $config;! }! }!
  • 27. CONSTRUCTOR INJECTION • It ensures that the choice of dependency is immutable • The constructor is only ever called once when the object is created, so you can be sure that the dependency will not change during the object's lifetime Pros Cons • It is not suitable for working with optional dependencies • Difficult to use in combination with class hierarchies
  • 28. CONSTRUCTOR INJECTION • Try to always use constructor injection • If dealing with legacy code that does not support it, consider using an adapter class with constructor injection • Depends on abstractions (interfaces), not concrete implementations TIPS
  • 29. SETTER INJECTION • Inject the dependencies via a setter method • The “injector” has to call the method in order to inject the dependency ! class LoggerChain implements SQLLogger! {! private $loggers = array();! ! public function setLogger(SQLLogger $logger)! {! $this->logger = $logger;! }! }!
  • 30. SETTER INJECTION • Works “well” with optional dependencies
 
 If you do not need the dependency, then just do not call the setter • You can call the setter multiple times.
 
 This is particularly useful if the method adds the dependency to a collection Pros Cons • Works “well” with optional dependencies 
 
 Are you sure you need optional dependencies? • You can call the setter multiple times • You are not sure if the dependency was set
  • 31. SETTER INJECTION TIPS • Avoid setter injections (the choice of dependencies is not inmutable) • If you do Dependency Inversion right, probably YANGI • Remember, your classes depend on abstractions, not concrete implementations, so you can use Null or Dummy implementations when necessary
  • 32. SETTER INJECTION EXAMPLE use PsrLogLoggerInterface;! ! final class OrderProcessor! {! private $logger;! ! function __construct(. . ., LoggerInterface $logger)! {! $this->logger = $logger;! }! }! ! final class GoodManLogger implements LoggerInterface {…}! ! final class LogstarLogger implements LoggerInterface {…}! ! final class NullLogger implements LoggerInterface {…}! Instead of having a setter method to inject the logger, use constructor injection and use the appropriate logger implementation in each case
  • 33. INTERFACE INJECTION • Define and use interfaces for the injection • Allows certain objects to be injected into other objects, that implement a common interface • It’s a kind of setter injection, so same pros and cons interface ContainerAwareInterface! {! public function setContainer(ContainerInterface $container = null);! }! ! class ContainerAwareEventDispatcher implements ContainerAwareInterface! {! public function setContainer(ContainerInterface $container = null)! {! }! }!
  • 34. PROPERTY INJECTION • Allows setting public fields of the class directly • There are mainly only disadvantages to using property injection, it is similar to setter injection but with additional important problems ! ! class NewsletterManager! {! public $mailer;! ! // ...! }!
  • 35. PROPERTY INJECTION • Useful if you are working with code that is out of your control, such as in a 3rd party library, which uses public properties for its dependencies Pros Cons • You cannot control when the dependency is set at all, it can be changed at any point in the object's lifetime • You cannot use type hinting so you cannot be sure what dependency is injected except by writing into the class code to explicitly test the class instance before using it
  • 36. SERVICES LOCATOR • Is an object that knows how to get all of the services that an another service might need interface CommandHandlerLocator ! {! public function locate($commandName);! }! ! ! class ContainerCommandHandlerLocator implements CommandHandlerLocator! {! private $container;! ! public function __construct(ContainerInterface $container)! {! $this->container = $container;! }! ! public function locate($commandName)! {! if (!$this->container->has($commandName)) {! throw new NotFoundException('Unable to find command handler');! }! ! return $this->container->get($commandName);! }! }!
  • 37. SERVICE LOCATOR • It’s easy to use and abuse due to its straightforward behaviour • Not all use cases are bad, for example when you want to load services on demand at runtime Pros Cons • It hides dependencies in your code making them difficult to figure out and potentially leads to errors that only manifest themselves at runtime • It becomes unclear what are the dependencies of a given class • It’s easy to abuse • It’s consider and anti-pattern (but there are valid use cases for it)
  • 38. SERVICE LOCATOR TIPS • Use a segregated interface for the locator, do not depends on the whole locator (container) ! • Limit the types of services a locator provides
  • 40. Dependency Injection Container is simply an object that manages the instantiation of other objects
  • 41. Automagically creates a given type with all the required dependencies
  • 42. DIC • Most of the time you do not need a DIC to benefit from Dependency Injection • But… creating and maintaining the dependencies by hand can become a nightmare pretty fast • A DIC manages objects from their instantiation to their configuration • The objects themselves should not know that they are managed by a container and should not know nothing about it
  • 44. PHP DIC IMPLEMENTATIONS • Twittee • Pimple • IlluminateDi • ZendDi • SymfonyDependencyInjection • http://php-di.org/ • Build your own
  • 46. The Dependency Injection component allows you to standardize and centralize the way objects are constructed in a SF application
  • 47. HOW IT WORKS? • Reads definition of how objects (services) should be constructed (XML,YAML, PHP, etc…) • Collects all definitions and builds a container • When requested, creates objects and injects the dependencies
  • 48. ADVANCED HOW IT WORKS • Compiler Passes • Container Extensions • Services Configurator • Tagged Services
  • 49. SYMFONY DIC • Basic (and advances) usages are well documented in the Symfony official documentation • Probably you are comfortable creating your own objects via the container • So, let’s try to solve the problems we stated at the beginning
  • 52. final class OrderProcessor! {! private $orderRepository;! private $lockSystem;! private $objectStore;! ! function __construct(! ! ! OrderRepository $repository, ! ! ! LockSystem $lockSystem, ! ! ! ObjectStore $objectStore)! {! $this->orderRepository = $repository;! $this->lockSystem = $lockSystem;! $this->objectStore = $objectStore;! }! }! ! final class ObjectStore! {! function __construct(StorageDriver $driver)! {! }! }! ! ! interface StorageDriver ! {}! ! final class RiakStorageDriver implements StorageDriver! {}! ! final class RedisStorageDriver implements StorageDriver! {}! ! final class InMemoryStorageDriver implements StorageDriver! {}! ! interface LockSystem ! {}! ! final class RedisLockSystem implements LockSystem! {}! ! final class ZooKeeperLockSystem implements LockSystem! {}! interface OrderRepository {}! ! final class MySqlOrderRepository implements OrderRepository! {}! ! final class CassandralOrderRepository implements OrderRepository! {}!
  • 53. services:! ! order_processor:! ! ! class: OrderProcessor! arguments:! ! ! ! object_store: @object_store! ! ! ! order_repository: [mysql|cassandra] ! ! ! ! lock_system: [redis|zookeeper]! ! ! ! ! ! ! object_store:! ! ! class: ObjectStore! ! ! arguments:! ! ! storage_driver: [riak|redis|inmemory]! MySqlOrderRepository CassandraOrderRepository RedisLockSystem ZooKeeperLockSystem RedisStorageDriver InMemoryStorageDriver RiakStorageDriver
  • 54. THE PROBLEM • It’s not possible to decide in advance what concrete implementation a deployment or environment is going to use • We must configure dependencies for all the concrete implementations and each one has their own dependencies
  • 55. SERVICES SETUP Deployment Environment Concrete Implementation DRAGON TESTING in-memory DRAGON STAGING/QA redis DRAGON PROD mysql MONSTER TESTING in-memory MONSTER PROD cassandra
  • 56. SOLUTIONS 1. Loading different configurations depending on the environment and aliasing services 2. Using synthetic services and aliases 3. Managing semantic configuration with extensions 4. Using a custom injector
  • 57. 1- LOADING DIFFERENT CONFIGURATIONS Load different configurations depending on the kernel.environment • Probably easy to setup for testing/dummy services Pros Cons • The choice is coupled to the environment • All services get defined, even when you are not going to use them
  • 58. ! namespace AcmeDemoBundleDependencyInjection;! ! use SymfonyComponentDependencyInjectionContainerBuilder;! use SymfonyComponentDependencyInjectionLoaderXmlFileLoader;! use SymfonyComponentHttpKernelDependencyInjectionExtension;! use SymfonyComponentConfigFileLocator;! ! class AcmeDemoExtension extends Extension! {! public function load(array $configs, ContainerBuilder $container)! {! $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));! $loader->load('services.yml');! ! if ($this->container->getParameter('kernel.environment') == 'test') {! $loader->load('services_test.yml');! }! }! ! public function getAlias()! {! return 'acme_demo';! }! }! services:! storage_driver:! alias: storage_driver.riak! ! storage_driver.riak:! class: RiakStorageDriver! ! storage_driver.redis:! class: RedisStorageDriver! ! storage_driver.memory:! class: InMemoryStorageDriver ! services:! storage_driver:! alias: storage_driver.memory! services.yml services_test.yml The choice is coupled to the environment
  • 59. 2- USING SYNTHETIC SERVICES AND ALIAS Define abstraction as “synthetic” services • Probably easy to setup for testing/dummy services Pros Cons • All services get defined, even when you are not going to use them • You have to define dependencies of services you probably not are going to use
  • 60. ! namespace AcmeDemoBundleDependencyInjection;! ! use SymfonyComponentDependencyInjectionContainerBuilder;! use SymfonyComponentDependencyInjectionLoaderXmlFileLoader;! use SymfonyComponentHttpKernelDependencyInjectionExtension;! use SymfonyComponentConfigFileLocator;! ! class AcmeDemoExtension extends Extension! {! public function load(array $configs, ContainerBuilder $container)! {! $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));! $loader->load('services.yml');! }! ! public function getAlias()! {! return 'acme_demo';! }! }! services:! storage_driver:! synthetic: true! ! storage_driver.riak:! class: RiakStorageDriver! ! storage_driver.redis:! class: RedisStorageDriver! ! storage_driver.memory:! class: InMemoryStorageDriver! ! services:! storage_driver:! alias: storage_driver.redis! services.yml app/config/config.yml ! services:! storage_driver:! alias: storage_driver.memory! app/config/config_test.yml
  • 61. 3- MANAGING SEMANTIC CONFIGURATIONS WITH EXTENSIONS • Instead of having the user override individual parameters, you let the user configure just a few, specifically created options. • As the bundle developer, you then parse through that configuration and load services inside an “Extension" • With this method, you won't need to import any configuration resources from your main application configuration: the Extension class can handle all of this
  • 62. 3- MANAGING SEMANTIC CONFIGURATIONS WITH EXTENSIONS • Good fit if you are building a bundle to be used by 3rd parties and you have a lot of configuration options you want to validate • Lot of boilerplate code • Extra complexity added • You just wanted to manage dependency injection right?
  • 63. class Configuration implements ConfigurationInterface! {! protected function addObjectStoreSection(ArrayNodeDefinition $rootNode)! {! $rootNode! ->children()! ->arrayNode('object_store')! ->isRequired()! ->children()! ->arrayNode('store_driver')! ->children()! ->scalarNode('type')->isRequired()->end()! ->scalarNode('connection')->end()! ->scalarNode('create_buckets')->end()! ->end()! ->validate()! ->ifTrue(function ($v) {! switch ($v['type']) {! case 'doctrine_dbal':! if (!isset($v['connection']) ||! !isset($v['create_buckets'])) {! return true;! }! break;! }! ! return false;! })! ->thenInvalid('children configuration')! ->end()! ->isRequired()! ->end()! ->end()! ->end();! }! } ! $definition = $container->getDefinition($storageDriver);! switch ($config['storage_driver']['type']) {! case 'doctrine_dbal':! $definition->replaceArgument(0, new Reference($config['storage_driver']['connection']));! $definition->replaceArgument(1, $config['storage_driver']['create_buckets']);! break;! case 'redis':! $definition->replaceArgument(0, new Reference($config['storage_driver']['connection']));! break;! }! ! $container->setAlias('object_storage.driver', $storageDriver);! Configuration Extension
  • 64. 3- MANAGING SEMANTIC CONFIGURATIONS WITH EXTENSIONS • More powerful than simply defining parameters: a specific option value might trigger the creation of many service definitions; • Ability to have configuration hierarchy • Smart merging of several config files (e.g. config_dev.yml and config.yml) Pros Cons • Too much verbose, a lot of boilerplate code • Extra complexity added if you only want to define dependencies and not a long configuration tree !
  • 65. 4- USING A CUSTOM INJECTOR • Let’s forget the Symfony DIC for a moment • Let’s go back to dependency management problem again
  • 66. services:! ! order_processor:! ! ! class: OrderProcessor! arguments:! ! ! ! object_store: @object_store! ! ! ! order_repository: [mysql|cassandra] ! ! ! ! lock_system: [redis|zookeeper]! ! ! ! ! ! ! object_store:! ! ! class: ObjectStore! ! ! arguments:! ! ! storage_driver: [riak|redis|inmemory]! MySqlOrderRepository CassandraOrderRepository RedisLockSystem ZooKeeperLockSystem RedisStorageDriver InMemoryStorageDriver RiakStorageDriver
  • 67. “A good architecture allows you to defer critical decisions, it doesn’t force you to defer them. However, if you can defer them, it means you have lots of flexibility” ! Uncle Bob
  • 68. “A good architecture allows volatile decisions to be easily changed” ! Uncle Bob
  • 69. services:! ! order_processor:! ! ! class: OrderProcessor! arguments:! ! ! ! object_store: @object_store! ! ! ! order_repository: [mysql|cassandra] ! ! ! ! lock_system: [redis|zookeeper]! ! ! ! ! ! ! object_store:! ! ! class: ObjectStore! ! ! arguments:! ! ! storage_driver: [riak|redis|inmemory]! MySqlOrderRepository CassandraOrderRepository RedisLockSystem ZooKeeperLockSystem RedisStorageDriver InMemoryStorageDriver RiakStorageDriver We need a dependency injection tool that allow us to easily change volatile decisions We need a dependency injection tool that allow us to defer critical decisions
  • 70. Core Framework Payment Component Analytics Component Object Store Lock System Component Exports Depends On Unit Of Work Storage Driver Order Processor Product Repository Order Repository Payment Gateway Tracker Tracker Queue Implementations Redis, Zookeper Redis, C*, Riak, MySql Config, API MySql, C* Itunes, Facebook, Amazon, Google Play Redis, RabbitMQ, SQS, Kinesis
  • 71. Core Component Object Store Lock System Component Exports Depends On Unit Of Work Storage Driver Implementations Redis, Zookeper Redis, C*, Riak, MySql namespace SPCoreGameFramework;! ! class FrameworkComponent implements Component! {! public function getName()! {! return 'core.game.framework';! }! ! public function getServicesDefinition()! {! return ServicesDefinition::create()! ! ->dependsOn('storage_driver')! ->withInstanceOf('SPCoreComponentObjectStoreStorageStorageDriver')! ! ->dependsOn('lock_system')! ->withInstanceOf('SPCoreComponentLockLockSystem')! ! ->exports('object_store')! ->withClass('SPCoreComponentObjectStore')! ->andConstructorDependencies(‘storage_driver’, ‘lock_system’);! }! } These are the decisions the component developer wants to defer Depends on abstractions, not concrete implementations
  • 72. Payment Component Order Processor Product Repository Order Repository Config, API MySql, C* namespace SPCoreGamePayment;! ! class PaymentComponent implements Component! {! public function getName()! {! return 'core.game.payment';! }! ! public function getServicesDefinition()! {! return ServicesDefinition::create()! ! ! ! ! ->dependsOn('product_repository')! ->withInstanceOf('SPCoreGamePaymentProductRepositoryProductRepository')! ! ->dependsOn('order_repository')! ->withInstanceOf('SPCoreGamePaymentOrderRepositoryOrderRepository')! ! ->dependsOn('gateways')! ->withInstanceOf('GatewayDefinition')! ! ->exports(‘order_processor')! ->withClass('SPCoreGamePaymentOrderProcessor')! ->andConstructorDependencies(‘gateways', ‘product_repository’, ‘order_repository’);! }! } Itunes, Facebook, Amazon, Google Play Payment Gateway These are the decisions the component developer wants to defer Depends on abstractions, not concrete implementations
  • 73. WHERE DOESTHE MAGIC COME FROM? • There is not such “magic” • Each component define it’s dependencies in a easy and legible way • Framework agnostic dependency definition • Based on the dependency definitions, services are added to the container during the container building phase
  • 74. HOW? • First, collect services definitions from installed components • Second, inject services definitions into the Symfony (or Silex, or…) container • No magic, just code !!!
  • 75. USING SYMFONY • Collect services definitions from installed components • Inject services definitions into the Symfony (or Silex, or…) container
  • 76. use SymfonyComponentDependencyInjection as DI;! ! ! final class ComponentDependencyInjector implements DICompilerCompilerPassInterface! {! private $components;! ! public function __construct(array $components = array())! {! $this->components = $components;! }! ! public function process(ContainerBuilder $container)! {! foreach ($this->components as $component) {! $services = $component->getServicesDefinition();! foreach ($services->dependencies as $definition) {! $id = $component->getName() . '.' . $definition->name;! ! if (!$container->has($id)) {! throw new ServiceNotFoundException($component->getName(), $id, $definition->instanceOf);! }! }! }! }! ! public function registerComponentsDependencies(ContainerBuilder $container)! {! foreach ($this->components as $component) {! $this->addComponentDependencies($container, $component);! }! }! ! private function addComponentDependencies(ContainerBuilder $container, Component $component)! {! $container->addObjectResource($component);! $services = $component->getServicesDefinition();! foreach ($services->exports as $definition) {! $this->addDefinition($container, $definition, $component);! }! foreach ($services->definitions as $definition) {! $this->addDefinition($container, $definition, $component);! }! }! ! $def = new DIDefinition($definition->class, $args);! $def->setPublic($definition->public);! ! $container->setDefinition($component->getName() . '.' . $definition->name, $def);! }! }! The Component Dependency Injector responsibility is to inject the definitions in a given container
  • 77. use SymfonyComponentHttpKernelKernel;! use SymfonyComponentConfigLoaderLoaderInterface;! ! class AppKernel extends Kernel! {! public function registerBundles()! {! $bundles = array(! new SymfonyBundleFrameworkBundleFrameworkBundle(),! new SymfonyBundleMonologBundleMonologBundle(),! ! new SPCoreBundleGameFrameworkBundleGameFrameworkBundle([! new SPCoreGameFrameworkFrameworkComponent(),! new SPCoreGameFrameworkPaymentComponent(),! new SPCoreGameFrameworkAnalyticsComponent(),! ]),! );! }! }! The AppKernel These are the framework agnostic components that provide infrastructure and logic for the application
  • 78. namespace SPCoreBundleGameFrameworkBundle;! ! use SymfonyComponentDependencyInjectionCompilerPassConfig;! use SymfonyComponentHttpKernelBundleBundle;! use SymfonyComponentDependencyInjectionContainerBuilder;! ! use SPCoreBridgeSymfonyDependencyInjectionComponentDependencyInjector;! ! class GameFrameworkBundle extends Bundle! {! private $components;! ! function __construct(array $components = array())! {! $this->components = $components;! }! ! public function build(ContainerBuilder $container)! {! $injector = new ComponentDependencyInjector($this->components);! $injector->registerComponentsDependencies($container);! ! $container->addCompilerPass($injector, PassConfig::TYPE_BEFORE_REMOVING);! }! }! The (in)Famous Framework Bundle The components we are “installing” in this application The “magic” is here !!!
  • 79. [SPCoreBridgeSymfonyDependencyInjectionExceptionServiceNotFoundException] Component "core.game.framework" requires the service "core.game.framework.lock_system" as an implementation of "SPCoreComponentLock LockSystem" but the service is not defined. [SPCoreBridgeSymfonyDependencyInjectionExceptionServiceNotFoundException] Component "core.game.payment" requires the service “core.game.payment.product_repository” as an implementation of "ProductRepository" but the service is not defined. $ php app/console The application complains about missing dependencies required by installed components We are planning to add suggested implementations for this requirement
  • 80. services:! ! ! core.game.framework.lock_system:! ! ! public: false! ! ! class: SPCoreComponentLockRedisLockSystem! ! ! arguments:! redis: @sp.core.redis.connector.lock! timeout: 10! expiration: 10! config.yml services:! ! ! core.game.framework.lock_system:! ! ! public: false! ! ! class: SPCoreComponentLockMemoryLockSystem! ! ! arguments:! timeout: 10! expiration: 10! config_test.yml The application config Concrete implementation in the application (by default) Semantic configuration? Concrete implementation in the application (test mode)
  • 81. 4- USING A CUSTOM INJECTOR • Complete control of component dependencies • Very limited in features (only constructor injections allowed) • Framework agnostic • Allows you to defer critical and volatile decisions Pros Cons • Very limited in features (only constructor injections allowed) ! !
  • 82. CONCLUSIONS • You have the tools, use the best tool that solve your problems • Don’t be afraid of going to the limit with the framework you use • The framework is just an implementation detail
  • 83. SOME REFERENCES http://www.objectmentor.com/resources/articles/dip.pdf ! http://www.martinfowler.com/articles/injection.html ! http://martinfowler.com/articles/dipInTheWild.html ! http://desymfony.com/ponencia/2013/inyeccion-dependencias-aplicaciones-php ! http://fabien.potencier.org/article/11/what-is-dependency-injection ! http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container ! http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container ! https://www.youtube.com/watch?v=IKD2-MAkXyQ ! http://richardmiller.co.uk/2014/03/12/avoiding-setter-injection/ ! http://richardmiller.co.uk/2014/03/28/symfony2-configuring-different-services-for-different-environments/ ! http://www.infoq.com/news/2013/07/architecture_intent_frameworks ! http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html !
  • 84. THANK YOU For your attention @ronnylt