Stephan Hochdörfer // 25.01.2014
IoC is a good idea. Like all good ideas, it should be used with moderation.
— Uncle Bob Martin (@unclebobmartin) 5. März 2013
class MyController { public function myAction() { return new Response('Ok!'); } }
class MyController { protected $service; public function __construct(MyService $service) { $this->service = $service; } public function myAction() { $this->service->doSomething(); return new Response('Ok!'); } }
class MyController { protected $service; protected $logger; public function __construct(MyService $service, MyLogger $logger) { $this->service = $service; $this->logger = $logger; } public function myAction() { $this->logger->debug('myAction running'); $this->service->doSomething(); return new Response('Ok!'); } }
class MyController { protected $service; protected $logger; public function __construct(Container $container) { $this->service = $container->get('service'); $this->logger = $container->get('logger'); } public function myAction() { $this->logger->debug('myAction running'); $this->service->doSomething(); return new Response('Ok!'); } }
class MyController implements ContainerAware { protected $service; protected $logger; public function setContainer(Container $container) { $this->service = $container->get('service'); $this->logger = $container->get('logger'); } public function myAction() { $this->logger->debug('myAction running'); $this->service->doSomething(); return new Response('Ok!'); } }
ContainerAware is the new Singleton. #php
— Tobias Schlitt (@tobySen) 14. September 2013
Let your code be as portable as possible.
class UserController { protected $auth; protected $user; public function __construct(AuthService $auth, UserService $user) { $this->auth = $auth; $this->user = $user; } public function authAction(Request $r) { return $this->auth->login($r->get('user'), $r->get('pass')); } public function listUserAction() { return $this->user->readAll(); } }
Rethink your architecture
and avoid fat controllers!
Architecture and structure
are agnostic of tools.
A ServiceLocator will not fix your
problem. It will help you to hide it.
class LoginController { protected $auth; public function __construct(AuthService $auth) { $this->auth = $auth; } public function authAction(Request $r) { return $this->auth->login($r->get('user'), $r->get('pass')); } }
class LoginController { protected $auth; public function __construct(AuthInterface $auth) { $this->auth = $auth; } public function authAction(Request $r) { return $this->auth->login($r->get('user'), $r->get('pass')); } }
class AuthService implements AuthInterface { protected $provider; public function __construct(AuthProviderInterface $provider) { $this->provider = $provider; } }
class AuthService implements AuthInterface { protected $provider; public function __construct(AuthProviderInterface $provider) { $this->provider = $provider; } public static function getInstance() { return new AuthService(new DatabaseAuthProvider()); } }
class AuthService implements AuthInterface { protected $provider; public function __construct(AuthProviderInterface $provider) { $this->provider = $provider; } }
class AuthServiceFactory { public static function getInstance() { return new AuthService(new DatabaseAuthProvider()); } }
@jclermont Funny enough, @shochdoerfer has already suggested that more than once.
— weierophinney (@mwop) 20. Dezember 2013
class UserController { protected $auth; public function __construct(AuthInterface $auth) { $this->auth = $auth; } }
Immutability rocks!
class UserController { protected $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function authAction(Request $r) { if (null !== $this->logger) { $this->logger->debug('Auth action running...'); } } }
class UserController { protected $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function authAction(Request $r) { if ($this->logger instanceof Logger) { $this->logger->debug('Auth action running...'); } } }
Setter injection means
null checks everywhere!
Avoid circular dependencies!
interface LoggerAware { public function setLogger(Logger $logger); }
class UserController implements LoggerAware { protected $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function authAction(Request $r) { if (null !== $this->logger) { $this->logger->debug('Auth action running...'); } } }
Interface injection is
"forced" setter injection.
class UserController { /** * @var Logger * @Inject */ public $logger; public function authAction(Request $r) { if (null !== $this->logger) { $this->logger->debug('Auth action running...'); } } }
class UserController { /** * @var Logger * @Inject */ protected $logger; public function authAction(Request $r) { if (null !== $this->logger) { $this->logger->debug('Auth action running...'); } } }
Can you explain the difference?
interface MyServiceLocator { /** * Returns an entry of the container by its $id. * * @param string $id * @throws NotFoundException * @return mixed */ public function get($id); /** * Returns true if the container can return an entry * for the given identifier. Returns false otherwise. * * @param string $id * @return bool */ public function has($id); }
interface MyDependencyContainer { /** * Returns an entry of the container by its $id. * * @param string $id * @throws NotFoundException * @return mixed */ public function get($id); /** * Returns true if the container can return an entry * for the given identifier. Returns false otherwise. * * @param string $id * @return bool */ public function has($id); }
Push vs. Pull
class MyController { protected $service; protected $logger; public function __construct(MyServiceLocator $locator) { $this->service = $locator->get('service'); $this->logger = $locator->get('logger'); } }
class MyController { protected $service; protected $logger; public function __construct(MyService $service, MyLogger $logger) { $this->service = $service; $this->logger = $logger; } }
internal Configuration
vs.
external Configuration
class AuthService implements AuthInterface { protected $provider; /** * @Inject */ public function __construct(AuthProviderInterface $provider) { $this->provider = $provider; } }
class AuthService implements AuthInterface { protected $provider; /** * @Inject * @Named('RESTAuthProvider') */ public function __construct(AuthProviderInterface $provider) { $this->provider = $provider; } }
/** * @ImplementedBy(RESTAuthProvider) */ interface AuthProviderInterface { /** * Tries to authenticate the user. * * @return bool */ public function authenticate($username, $password); }
services: authprovider: class: RESTAuthProvider authservice: class: AuthService arguments: ["@authprovider"]
services: provider_rest: class: RESTAuthProvider provider_soap: class: SoapAuthProvider authservice_rest: class: AuthService arguments: ["@provider_rest"] authservice_soap: class: AuthService arguments: ["@provider_soap"]
A mix of internal and external
configuration can make sense.
$dep = new \Di\Definition\Builder\PhpClass(); $dep->setName('RESTAuthProvider'); $class = new \Di\Definition\Builder\PhpClass(); $class->setName('AuthService'); $im = new \Di\Definition\Builder\InjectionMethod(); $im->setName('__construct'); $im->addParameter('provider', 'RESTAuthProvider'); $class->addInjectionMethod($im); $builder = new \Di\Definition\BuilderDefinition(); $builder->addClass($dep); $builder->addClass($class); $defList = new \Di\DefinitionList($builder); $di = new \Di\DiContainer($defList);
new AuthService(new RESTAuthProvider());
If your Container configuration is too complex
you are using the wrong container!
DI is not slow. Use a PHP based configuration!
PHP based configuration means:
No calls to the Reflection API!
Choose whatever works
for you and your use-case.
DI is so much more than *just*
being useful for testing.
Thank you!
Do not forget to rate the talk:
https://joind.in/10294