Post on 14-Apr-2018
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
1/103
Dependency Injection
Fabien Potencier
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
2/103
Fabien Potencier
Serial entrepreneurDeveloper by passion
Founder of Sensio
Creator and lead developer of Symfony
On Twitter @fabpot
On github http://www.github.com/fabpotBlog http://fabien.potencier.org/
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
3/103
Dependency Injection
A real world web example
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
4/103
In most web applications, you need to manage the user preferences
The user language Whether the user is authenticated or not The user credentials
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
5/103
This can be done with a User object
setLanguage(), getLanguage()setAuthenticated(), isAuthenticated()addCredential(), hasCredential()...
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
6/103
The User informationneed to be persisted
between HTTP requests
We use the PHP session for the Storage
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
7/103
class SessionStorage{
function __construct($cookieName = 'PHP_SESS_ID'){
session_name($cookieName);session_start();
}
function set($key, $value){
$_SESSION[$key] = $value;}
// ...}
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
8/103
class User{
protected $storage;
function __construct(){
$this->storage = new SessionStorage();}
function setLanguage($language){
$this->storage->set('language', $language);}
// ...}
$user = new User();
Veryeasytouse
Veryhardto
customize
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
9/103
class User{
protected $storage;
function __construct($storage){
$this->storage = $storage;}
}
$storage = new SessionStorage();
$user = new User($storage);Slightlymore
difficulttouse
Veryeasyto
customize
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
10/103
Thats Dependency Injection
Nothing more
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
11/103
Lets understand why the first example
is not a good idea
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
12/103
I want to change the session cookie name
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
13/103
class User{
protected $storage;
function __construct(){
$this->storage = new SessionStorage('SESSION_ID');}
function setLanguage($language){$this->storage->set('language', $language);
}
// ...}
$user = new User();
Hardcodeitinthe
Userclass
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
14/103
class User{
protected $storage;
function __construct(){
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);}
}
define('STORAGE_SESSION_NAME', 'SESSION_ID');
$user = new User();
Addaglobal
configuration?
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
15/103
class User{
protected $storage;
function __construct($sessionName){
$this->storage = new SessionStorage($sessionName);}
}
$user = new User('SESSION_ID');
ConfigureviaUser?
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
16/103
class User
{protected $storage;
function __construct($storageOptions){
$this->storage = new SessionStorage($storageOptions['session_name']);
$user = new User(array('session_name' => 'SESSION_ID')
); Configurewithanarray?
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
17/103
I want to change the session storage implementation
Filesystem
MySQLMemcached
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
18/103
class User{
protected $storage;
function __construct(){
$this->storage = Registry::get('session_storage');
}}
$storage = new SessionStorage();Registry::set('session_storage', $storage);
$user = new User();
Useaglobal
registryobject?
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
19/103
Now, the User depends on the Registry
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
20/103
Instead of harcoding
the Storage dependency
inside the User class constructor
Inject the Storage dependency
in the User object
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
21/103
class User
{protected $storage;
function __construct($storage){
$this->storage = $storage;}}
$storage = new SessionStorage('SESSION_ID');$user = new User($storage);
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
22/103
What are the advantages?
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
23/103
Use different Storage strategies
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
24/103
class User
{ protected $storage;
function __construct($storage){
$this->storage = $storage;}}
$storage = new MySQLSessionStorage('SESSION_ID');$user = new User($storage);
Useadifferent
Storageengine
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
25/103
Configuration becomes natural
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
26/103
class User
{ protected $storage;
function __construct($storage){
$this->storage = $storage;}}
$storage = new MySQLSessionStorage('SESSION_ID');$user = new User($storage);
Configuration
isnatural
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
27/103
Wrap third-party classes (Interface / Adapter)
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
28/103
class User{
protected $storage;
function __construct(SessionStorageInterface $storage){
$this->storage = $storage;}
}
interface SessionStorageInterface{
function get($key);
function set($key, $value);}
Addaninterface
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
29/103
Mock the Storage object (for testing)
class User{
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
30/103
{protected $storage;
function __construct(SessionStorageInterface $storage)
{$this->storage = $storage;
}}
class SessionStorageForTests implements SessionStorageInterface{
protected $data = array();
static function set($key, $value)
{self::$data[$key] = $value;
}}
MocktheSession
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
31/103
Use different Storage strategies
Configuration becomes natural
Wrap third-party classes (Interface / Adapter)
Mock the Storage object (for testing)
Easy without changing the User class
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
32/103
Dependency Injection is where
components are given their dependencies
through their constructors, methods, ordirectly into fields.
http://www.picocontainer.org/injection.html
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
33/103
$storage = new SessionStorage();
// constructor injection$user = new User($storage);
// setter injection
$user = new User();$user->setStorage($storage);
// property injection
$user = new User();$user->storage = $storage;
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
34/103
A slightly more complex
web example
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
35/103
class Application{
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
36/103
{function __construct(){
$this->request = new WebRequest();
$this->response = new WebResponse();
$storage = new FileSessionStorage('SESSION_ID');$this->user = new User($storage);
$cache = new FileCache(array('dir' => dirname(__FILE__).'/cache')
);$this->routing = new Routing($cache);
}
}
$application = new Application();
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
37/103
Back to square 1
class Application{
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
38/103
{function __construct(){
$this->request = new WebRequest();
$this->response = new WebResponse();
$storage = new FileSessionStorage('SESSION_ID');$this->user = new User($storage);
$cache = new FileCache(array('dir' => dirname(__FILE__).'/cache')
);$this->routing = new Routing($cache);
}
}
$application = new Application();
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
39/103
We need a Container
Describes objects
and their dependencies
Instantiates and configures
objects on-demand
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
40/103
A container
SHOULD be able to manage
ANY PHP object (POPO)
The objects MUST not know
that they are managed
by a container
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
41/103
Parameters The SessionStorageInterface implementation we want to use (the class name) The session name
Objects SessionStorage User
Dependencies User depends on a SessionStorageInterface implementation
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
42/103
Lets build a simple container with PHP 5.3
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
43/103
DI Container
Managing parameters
class Container
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
44/103
class Container{
protected $parameters = array();
public function setParameter($key, $value){
$this->parameters[$key] = $value;
}
public function getParameter($key){
return $this->parameters[$key];}
}
Decoupling
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
45/103
$container = new Container();$container->setParameter('session_name', 'SESSION_ID');$container->setParameter('storage_class', 'SessionStorage');
$class = $container->getParameter('storage_class');$sessionStorage = new $class($container->getParameter('session_name'));
$user = new User($sessionStorage);
Decoup g
Customization
Objectscreation
class Container Using PHP
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
46/103
class Container{
protected $parameters = array();
public function __set($key, $value){
$this->parameters[$key] = $value;
}
public function __get($key){
return $this->parameters[$key];}
}
UsingPHP
magicmethods
Interface
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
47/103
$container = new Container();$container->session_name = 'SESSION_ID';$container->storage_class = 'SessionStorage';
$sessionStorage = new $container->storage_class($container->session_name);$user = new User($sessionStorage);
Int
iscleaner
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
48/103
DI Container
Managing objects
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
49/103
We need a way to describe how to create objects,
without actually instantiating anything!
Anonymous functions to the rescue!
Anonymous Functions / Lambdas
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
50/103
y
A lambda is a function
defined on the fly
with no name
function () { echo 'Hello world!'; };
Anonymous Functions / Lambdas
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
51/103
y
A lambda can be stored
in a variable
$hello = function () { echo 'Hello world!'; };
Anonymous Functions / Lambdas
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
52/103
y
And then it can be used
as any other PHP callable
$hello();
call_user_func($hello);
Anonymous Functions / Lambdas
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
53/103
y
You can also pass a lambda
as an argument to a function or method
function foo(Closure $func){$func();
}
foo($hello);
Fonctions anonymes
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
54/103
y$hello = function ($name) { echo 'Hello '.$name; };
$hello('Fabien');
call_user_func($hello, 'Fabien');
function foo(Closure $func, $name){
$func($name);}
foo($hello, 'Fabien');
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
55/103
DI Container
Managing objects
class Container{protected $parameters = array();protected $objects array();
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
56/103
protected $objects = array();
public function __set($key, $value){
$this->parameters[$key] = $value;}
public function __get($key){
return $this->parameters[$key];
}
public function setService($key, Closure $service){
$this->objects[$key] = $service;}
public function getService($key){
return $this->objects[$key]($this);}
}
Storealambda
abletocreatethe
objecton-demand
Asktheclosuretocreatetheobjectandpassthe
currentContainer
$container = new Container();$container >session name = 'SESSION ID';
Description
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
57/103
$container->session_name = SESSION_ID ;$container->storage_class = 'SessionStorage';$container->setService('user', function ($c)
{return new User($c->getService('storage'));
});$container->setService('storage', function ($c){
return new $c->storage_class($c->session_name);});
$user = $container->getService('user');
CreatingtheUserisnowaseasyasbefore
Descrip
class Container{
protected $values array(); Simplifythecode
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
58/103
protected $values = array();
function __set($id, $value)
{ $this->values[$id] = $value;}
function __get($id){
if (is_callable($this->values[$id])){
return $this->values[$id]($this);}else{
return $this->values[$id];}
}}
Simplify
$container = new Container();$container >session name = 'SESSION ID';
Unifiedinterface
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
59/103
$container->session_name = SESSION_ID ;$container->storage_class = 'SessionStorage';$container->user = function ($c){
return new User($c->storage);};$container->storage = function ($c)
{return new $c->storage_class($c->session_name);
};
$user = $container->user;
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
60/103
DI Container
Scope
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
61/103
For some objects, like the user,
the container must alwaysreturn the same instance
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
62/103
spl_object_hash($container->user)
!==spl_object_hash($container->user)
$container->user = function ($c)
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
63/103
$container->user = function ($c){
static $user;
if (is_null($user)){
$user = new User($c->storage);}
return $user;};
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
64/103
spl_object_hash($container->user)
===spl_object_hash($container->user)
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
65/103
$container->user = $container->asShared(function ($c){
return new User($c->storage);});
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
66/103
A closure is a lambda
that remembers the contextof its creation
class Article{
( )
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
67/103
public function __construct($title){
$this->title = $title;}
public function getTitle(){
return $this->title;}
}
$articles = array(new Article('Title 1'),new Article('Title 2'),
);
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
68/103
$mapper = function ($article){
return $article->getTitle();
};
$titles = array_map($mapper, $articles);
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
69/103
$method = 'getTitle';
$mapper = function ($article) use($method){
return $article->$method();
};
$method = 'getAuthor';
$titles = array_map($mapper, $articles);
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
70/103
$mapper = function ($method){
return function ($article) use($method){
return $article->$method();};
};
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
71/103
$titles = array_map($mapper('getTitle'), $articles);
$authors = array_map($mapper('getAuthor'), $articles);
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
72/103
$container->user = $container->asShared(function ($c){
return new User($c->storage);});
function asShared(Closure $lambda){
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
73/103
return function ($container) use ($lambda)
{static $object;
if (is_null($object)){
$object = $lambda($container);}
return $object;};}
class Container{protected $values = array();
function set($id $value)
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
74/103
function __set($id, $value){
$this->values[$id] = $value;
}
function __get($id){
if (!isset($this->values[$id])){
throw new InvalidArgumentException(sprintf('Value "%s" is not defined.', $id));
}
if (is_callable($this->values[$id])){
return $this->values[$id]($this);}else{
return $this->values[$id];}
}}
Errormanagement
class Container
{protected $values = array();
function __set($id, $value){
$this->values[$id] = $value;
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
75/103
}
function __get($id){
if (!isset($this->values[$id])){throw new InvalidArgumentException(sprintf('Value "%s" is not defined.', $id));
}
if (is_callable($this->values[$id])){
return $this->values[$id]($this);}else{
return $this->values[$id];}
}
function asShared($callable){
return function ($c) use ($callable){
static $object;
if (is_null($object)){
$object = $callable($c);}return $object;
};}
}
40LOCforafully-
featuredcontainer
Im NOT advocating
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
76/103
g
the usage of lambdas everywhere
This presentation is about
showing how they work
on practical examples
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
77/103
A DI Container
does NOT manage
ALL your objects
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
78/103
Good rule of thumb:It manages Global objects
Objects with only one instance (!= Singletons)
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
79/103
LIKE
a User, a Request,
a database Connection, a Logger,
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
80/103
UNLIKE
Model objects (a Product, a blog Post, )
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
81/103
Symfony Components
Dependency Injection
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
82/103
Rock-solid implementation of a DIC in PHP 5.3
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
83/103
At the core of the Symfony 2.0 framework
which is one of the fastest framework
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
84/103
Very flexible
Configuration in PHP, XML, YAML, or INI
$container = new Builder();
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
85/103
$container = new Builder();
$container->register('output', 'FancyOutput');$container->
register('message', 'Message')->setArguments(array(
new sfServiceReference('output'),array('with_newline' => true)
));
$container->message->say('Hello World!');
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
86/103
services:output: { class: FancyOutput }message:
class: Message
arguments:- @output- { with_newline: true }
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
87/103
true
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
88/103
imports:- { resource: parameters.yml }- { resource: parameters.ini }- { resource: services.xml }
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
89/103
As fast as it can be
The container can be
compiled down
to plain PHP code
use Symfony\Components\DependencyInjection\Container;use Symfony\Components\DependencyInjection\Reference;use Symfony\Components\DependencyInjection\Parameter;
class ProjectServiceContainer extends Container{
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
90/103
{protected $shared = array();
protected function getOutputService(){
if (isset($this->shared['output'])) return $this->shared['output'];
$instance = new FancyOutput();
return $this->shared['output'] = $instance;}
protected function getMessageService(){
if (isset($this->shared['message'])) return $this->shared['message'];
$instance = new Message($this->getOutputService(), array('with_newline' => true));
return $this->shared['message'] = $instance;}
}
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
91/103
Semantic configuration
Thanks to an extension mechanism
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
92/103
p y y p j g
fabien.potencierxxxxxx
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
93/103
xmlns:doctrine="http://www.symfony-project.org/schema/dic/doctrine" xmlns:zend="http://www.symfony-project.org/schema/dic/zend"
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd
http://www.symfony-project.org/schema/dic/doctrine http://www.symfony-project.org/schema/dic/doctrine/doctrine-1.0.xsd
http://www.symfony-project.org/schema/dic/swiftmailer http://www.symfony-project.org/schema/dic/swiftmailer/swiftmailer-1.0.xsd" >
fabien.potencierxxxxxx
auto-completion
andvalidation
withXSD
zend.logger:level: debug
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
94/103
level: debug
path: %kernel.root_dir%/logs/%kernel.environment%.log
doctrine.dbal:dbname: dbnameusername: root
password: ~
swift.mailer:transport: gmailusername: fabien.potencier
password: xxxxxxx
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
95/103
Everything is converted by the extension
to plain services and parametersno overhead
Loader::registerExtension(new SwiftMailerExtension());Loader::registerExtension(new DoctrineExtension());Loader::registerExtension(new ZendExtension());
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
96/103
Loader::registerExtension(new ZendExtension());
$loader = new XmlFileLoader(__DIR__);$config = $loader->load('services.xml');
$container = new Builder();$container->merge($config);
$container->mailer->...
$dumper = new PhpDumper($container);echo $dumper->dump();
More about Dependency Injection
http://fabien.potencier.org/article/17/on-php-5-3-lambda-functions-and-closures
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
97/103
http://components.symfony-project.org/dependency-injection/ (5.2)
http://github.com/fabpot/symfony/tree/master/src/Symfony/Components/DependencyInjection/(5.3)
http://github.com/fabpot/pimple
http://twittee.org/
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
98/103
Remember, most of the time,
you dont need a Container
to use Dependency Injection
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
99/103
You can start to use and benefit from
Dependency Injection today
by implementing it
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
100/103
y p g
in your projects
by using externals librariesthat already use DI
without the need of a container
Symfony
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
101/103
y y
Zend FrameworkezComponents
DoctrineSwift Mailer
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
102/103
Questions?
My slides will be available on
slideshare.com/fabpot
Sensio S.A.
92 98 boulevard Victor Hugo
8/2/2019 Dependency Injection Zendcon 2010 101104172213 Phpapp01
103/103
92-98, boulevard Victor Hugo
92 115 Clichy CedexFRANCE
Tl. : +33 1 40 99 80 80
Contact
Fabien Potencierfabien.potencier at sensio.com
http://www.sensiolabs.com/
http://www.symfony-project.org/
http://fabien.potencier.org/