Dependency Injection Smells
Featuring Zend,Symfonyand Doctrinecode
Matthias Noback - PHP developer and consultant
Dutch PHP Conference - 6/7/2013
Matthias Noback
Dutch developer
Consultancy, training, writing
Clean code
·
·
·
2/44
What is a dependency?
A needs a B to do its job
therefore
B is a dependency of A
3/44
Dependency management
To make sure that A will have a B, A can:
4/44
1. Create a B
5/44
2. Fetch a B
6/44
3. Receive a B
7/44
Dependency management
Strategy 1: Do it yourself
8/44
Why not...
Instantiate classes with the newoperator?
classSomeClass
{
publicfunctiondoSomething()
{
$logger=newLogger();
$logger->debug('Nothingtoseehere');
}
}
PHP
9/44
Why not...
Instantiate variable classes?
classSomeClass
{
publicfunction__construct($loggerClass)
{
$this->loggerClass=$loggerClass;
}
publicfunctiondoSomething()
{
$logger=new{$this->loggerClass}();
$logger->debug('Veryflexible,right?');
}
}
PHP
10/44
Why not...
Instantiate in overridable methods?
classSomeClass
{
publicfunctiondoSomething()
{
$logger=$this->createLogger();
$logger->debug('Matthiaswashere');
}
protectedfunctioncreateLogger()
{
returnnewLogger();
}
}
PHP
11/44
Dependency management
Strategy 2: Fetch your dependencies
12/44
Why not...
Use a global/static variable?
classSomeClass
{
publicfunctiondoSomething()
{
Logger::getInstance()->debug('Matthiaswashere');
}
}
PHP
13/44
Why not...
Use a service container/manager/registry?
classSomeClass
{
publicfunction__construct(ContainerInterface$container)
{
$this->container=$container;
}
publicfunctiondoSomething()
{
$this
->container
->get('logger')
->log('Wow,thatwasquiteadistance!')
}
}
PHP
14/44
Dependency management
Strategy 3: Be passive about your dependencies
15/44
Injecting dependencies by their interface
classSomeClass
{
publicfunction__construct(LoggerInterface$logger)
{
$this->logger=$logger;
}
publicfunctiondoSomething()
{
$this->logger->info('Ah,muchbetter');
}
}
PHP
16/44
Strategy review
1. Create your own dependencies
2. Fetch your dependencies
17/44
Strategy review
You are in control of your dependencies
18/44
Strategy review
3. Have your dependencies injected
19/44
Strategy review
You are not in control of your dependencies
Someone else
This is called "inversion of control" (IoC)
·
·
·
20/44
Dependency injection
Dependency injection is a ,
used to
21/44
Code review
ZendCrypt
Code review
ZendCrypt
classBlockCipher
{
publicfunction__construct(SymmetricInterface$cipher)
{
$this->cipher=$cipher;
}
publicfunctionencrypt($data)
{
...
}
publicfunctiondecrypt($data)
{
...
}
}
PHP
23/44
ZendCrypt
Dependency Injection Smell: Static dependency
Static dependencies are hard-to-control dependencies
classBlockCipher
{
publicstaticfunctionfactory($adapter,$options=array())
{
$plugins=static::getSymmetricPluginManager();
$adapter=$plugins->get($adapter,(array)$options);
returnnewstatic($adapter);
}
}
PHP
24/44
ZendCrypt
Dependency Injection Smell: Missing dependency auto-recovery
If you have a dependency, act like you are .
publicstaticfunctiongetSymmetricPluginManager()
{
if(static::$symmetricPlugins===null){
static::setSymmetricPluginManager(newSymmetricPluginManager());
}
returnstatic::$symmetricPlugins;
}
PHP
25/44
ZendCrypt
Dependency Injection Smell: Hidden dependencies
If you have a dependency, make it explicit.
classSymmetricPluginManagerextendsAbstractPluginManager
{
protected$invokableClasses=array(
'mcrypt'=>'ZendCryptSymmetricMcrypt',
);
}
PHP
26/44
ZendCrypt
Suggested refactoring
useZendCryptSymmetricMcrypt;
useZendCryptSymmetricBlockCipher;
$cipher=newMcrypt(array('algo'=>'aes'));
$blockCipher=newBlockCipher($cipher);
PHP
No SymmetricPluginManager
No factory()method
·
·
27/44
ZendCrypt
Dependency Injection Smell: Creation logic reduction
Make way for complex creation logic (and don't think the newoperator will
suffice).
publicstaticfunctionsetSymmetricPluginManager($plugins)
{
if(is_string($plugins)){
$plugins=new$plugins();
}
if(!$pluginsinstanceofSymmetricPluginManager){
thrownewExceptionInvalidArgumentException();
}
static::$symmetricPlugins=$plugins;
}
PHP
28/44
Code review
SymfonyBundleFrameworkBundle
SymfonyBundleFrameworkBundle
HttpCache
abstractclassHttpCacheextendsBaseHttpCache
{
publicfunction__construct(HttpKernelInterface$kernel,$cacheDir=null)
{
$this->kernel=$kernel;
$this->cacheDir=$cacheDir;
parent::__construct(
$kernel,
$this->createStore(),
$this->createEsi(),
array_merge(
array('debug'=>$kernel->isDebug()),
$this->getOptions())
);
}
}
PHP
30/44
SymfonyBundleFrameworkBundle
Dependency Injection Smell: Factory methods
Don't require developers to use inheritance for replacing dependencies.
abstractclassHttpCacheextendsBaseHttpCache
{
protectedfunctioncreateEsi()
{
returnnewEsi();
}
protectedfunctioncreateStore()
{
returnnewStore($this->cacheDir?:$this->kernel->getCacheDir().'/http_cache');
}
}
PHP
31/44
SymfonyBundleFrameworkBundle
Dependency Injection Smell: Programming against an implementation
HttpCachehas a dependency on HttpKernelInterface...
abstractclassHttpCacheextendsBaseHttpCache
{
publicfunction__construct(HttpKernelInterface$kernel,$cacheDir=null)
{
...
}
}
PHP
interfaceHttpKernelInterface
{
publicfunctionhandle(Request$request,$type=self::MASTER_REQUEST,$catch=true);
}
PHP
32/44
SymfonyBundleFrameworkBundle
Dependency Injection Smell: Programming against an implementation
... but it uses of the interface methods
array('debug'=>$kernel->isDebug()), PHP
returnnewStore($this->cacheDir?:$this->kernel->getCacheDir().'/http_cache'); PHP
$this->getKernel()->boot(); PHP
33/44
SymfonyBundleFrameworkBundle
Suggested refactoring
Program against an interface (strictly)
Or: program against an implementation (and type-hint accordingly)
Or: expand the interface to contain the methods you need
·
·
·
34/44
Code review
DoctrineDBAL
DoctrineDBAL
Dependency Injection Smell: Dependencies prohibited
abstractclassType
{
publicstaticfunctionaddType($name,$className)
{
self::$typesMap[$name]=$className;
}
publicstaticfunctiongetType($name)
{
if(!isset(self::$typeObjects[$name])){
self::$typeObjects[$name]=newself::$typesMap[$name]();
}
returnself::$typeObjects[$name];
}
}
Type::getType('datetime')->convertToPHPValue('2013-06-08');
PHP
36/44
DoctrineDBAL
Dependency Injection Smell: Dependencies prohibited
classEncryptedStringTypeextendsType
{
publicfunction__construct(BlockCipher$blockCipher)
{
$this->blockCipher=$blockCipher;
}
publicfunctionconvertToDatabaseValue($value)
{
return$this->blockCipher->encrypt($value);
}
publicfunctionconvertToPHPValue($value)
{
return$this->blockCipher->decrypt($value);
}
}
PHP
37/44
DoctrineDBAL
Dependency Injection Smell: Dependencies prohibited
We can not override Type::__construct()since it is final.
Don't neglect other classes' needs (even if your class has everything it needs)
Type::addType('encrypted_string','EncryptedStringType'); PHP
abstractclassType
{
finalprivatefunction__construct(){}
}
PHP
38/44
DoctrineDBAL
Dependency Injection Smell: Dependencies prohibited
Work-around: static dependency
classEncryptedStringType
{
publicstatic$blockCiper;
}
EncryptedStringType::$blockCipher=...;
PHP
39/44
DoctrineDBAL
Recommended refactoring
1. Split the Typeclass into an AbstractTypeand a TypeRegistry.
2. Set a type instance, instead of just a class.
$blockCipher=...;//ha,weknowhowtomakeone!
$typeRegistry=newTypeRegistry();
$encryptedStringType=newEncryptedStringType($blockCipher);
$typeRegistry->setType('encrypted_string',$encryptedStringType);
PHP
40/44
In conclusion
Good API design
Dependency injection smells
Static dependency
Missing dependency auto-recovery
Hidden dependencies
Creation logic reduction
Factory methods
Programming against an implementation
Dependencies prohibited
·
·
·
·
·
·
·
42/44
Keep in mind...
Be clear and open about what your dependencies are
Require only a minimum amount of dependencies
Develop with your users (other developers) in mind
·
·
·
43/44
Thank you
Please leave some feedback at joind.in/8447
twitter @matthiasnoback
www php-and-symfony.matthiasnoback.nl
github github.com/matthiasnoback
leanpub leanpub.com/a-year-with-symfony

Dependency Injection Smells