The Flip Side of Dependency Injection - The PHP Consulting ...

44
The Flip Side of Dependency Injection Arne Blankerts | PHP Conference, Berlin | June 3 rd 2014

Transcript of The Flip Side of Dependency Injection - The PHP Consulting ...

The Flip Side of Dependency InjectionArne Blankerts | PHP Conference, Berlin | June 3rd 2014

» Working with PHP for over a decade(Security paranoid ;) and System Architect)

» Author of phpab and phpDox

» Co-Founder and Consultantwith thePHP.cc

Arne Blankerts

The Problem1 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct() {9 $config$config = newnew Config('/path/to/some.ini');

10 $this$this->backend = newnew Backend($config$config);11 $this$this->service = newnew SuperService($config$config, 'Sample');12 }1314 }

Solution - Attempt 11 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct() {9 $config$config = Config::getInstance();

10 $this$this->backend = newnew Backend($config$config);11 $this$this->service = newnew SuperService($config$config), 'Sample');12 }1314 }

Singleton?

Singleton? Really?

Solution - Attempt 21 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct(Config $config$config) {9 $this$this->backend = newnew Backend($config$config);

10 $this$this->service = newnew SuperService($config$config), 'Sample');11 }1213 }

The phonebookapproach

Solution - Attempt 31 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct(Config $config$config) {9 $this$this->backend = newnew Backend(

10 $config$config->get('backend.credentials')11 );12 $this$this->service = newnew SuperService(13 $config$config->get('service.hostname')14 );15 }1617 }

Solution - Attempt 41 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct(Container $container$container) {9 $this$this->backend = $container$container->get('backend');

10 $this$this->service = $container$container->get('superservice');11 }1213 }

Dependency Injection1 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct(Backend $backend$backend, Service $service$service) {9 $this$this->backend = $backend$backend;

10 $this$this->service = $service$service;11 }1213 }

Almost there ...

Better Dependency Injection!1 <?php23 classclass Sample {45 privateprivate $backend$backend;6 privateprivate $service$service;78 public functionpublic function __construct(9 BackendInterface $backend$backend,

10 ServiceInterface $service$service11 ) {12 $this$this->backend = $backend$backend;13 $this$this->service = $service$service;14 }1516 }

Many dependencies?1 <?php23 classclass Sample {45 privateprivate $serviceA$serviceA;6 privateprivate $serviceB$serviceB;7 privateprivate $serviceC$serviceC;8 privateprivate $serviceD$serviceD;9 privateprivate $serviceE$serviceE;

1011 public functionpublic function __construct(12 ServiceAInterface $serviceA$serviceA, ServiceBInterface $serviceB$serviceB,13 ServiceCInterface $serviceC$serviceC, ServiceDInterface $serviceD$serviceD,14 ServiceEInterface $serviceE$serviceE15 ) {16 // [...]17 }1819 }

Too manydependencies

Getting thingsassembled

Magic?1 <?php23 classclass Sample {45 /**6 * @var ServiceInterface7 * @Inject8 */9 privateprivate $serviceA$serviceA;

1011 }

Advanced Magic?1 <?php23 classclass Sample {45 /**6 * @var ServiceInterface7 * @Inject("service.SuperServiceA")8 */9 privateprivate $serviceA$serviceA;

1011 }

Advanced advanced Magic?1 <?php23 classclass Sample {45 /**6 * @var ServiceInterface7 * @Inject("service.SuperServiceA", Instance="ThatInstance")8 */9 privateprivate $serviceA$serviceA;

1011 }

Extenral Configuration?1 services:2 serviceA:3 classclass: ServiceAImplementor4 serviceB:5 classclass: ServiceBImplementor6 serviceC:7 classclass: ServiceCImplementor

Still At Square One ...

What do we need?

Requirements• All Dependencies must be in code• Seperate Object creation from usage• Ability to choose actual implementation on runtime

Requirements• All Dependencies must be in code

• No hidden Dependencies• No external configuration• No framework magic based on annotations

• Seperate Object Creation from Usage• Ability to choose actual implementation on runtime

Requirements• All Dependencies must be in code• Seperate Object Creation from Usage• Ability to choose actual implementation on runtime

Requirements• All Dependencies must be in code• Seperate Object Creation from Usage• Ability to choose actual Implementation on Runtime

Requirements• All Dependencies must be in code• Seperate Object Creation from Usage• Ability to choose actual Implementation on Runtime

• Run consistent A/B-Tests• Degrade gracefully• Customized execution

How to do that in plain OOP?

Step 1

Factory

1 <?php23 classclass Factory {45 privateprivate $config$config;67 public functionpublic function __construct(Config $config$config) {8 $this$this->config = $config$config;9 }

1011 /**12 * @return ServiceA13 */14 public functionpublic function getServiceA() {15 return newreturn new ServiceA($this$this->getDBConnection());16 }1718 /**19 * @return DBConnection20 */21 private functionprivate function getDBConnection() {22 return newreturn new DBConnection($this$this->config->getDSN());23 }2425 }

Avoid injecting factories!

But what about runtimecomposing?

Locators!

1 <?php23 classclass FooLocator {45 /** @var Factory */6 privateprivate $factory$factory;78 public functionpublic function __construct(Factory $factory$factory) {9 $this$this->factory = $factory$factory;

10 }1112 /**13 * @return FooInterface14 */15 public functionpublic function get($type$type) {16 switchswitch ($type$type) {17 casecase 'a': returnreturn $this$this->factory->getServiceA();18 casecase 'b': returnreturn $this$this->factory->getServiceB();19 defaultdefault: throw newthrow new RuntimeException("Type '$type' not specified");20 }21 }2223 }

That's it?

What happend to A/BTesting?

https://github.com/theseer/Factory

Done?

Done.

sharing experience