2. Fabien Potencier
• Serial entrepreneur and developer by passion
• Founder of Sensio (in 1998)
– A services and consulting company
specialized in Web technologies
and Internet marketing (France and USA)
– 70 people
– Open-Source specialists
– Big corporate customers
– Consulting, training, development, web design, … and more
– Sponsor of a lot of Open-Source projects
like symfony and Doctrine
3. Fabien Potencier
• Creator and lead developer of symfony…
• and creator and lead developer of some more OSS:
– symfony components
– Swift Mailer : Powerful component based mailing library for PHP
– Twig : Fexible, fast, and secure template language for PHP
– Pirum : Simple PEAR Channel Server Manager
– Sismo : PHP continuous integration server
– Lime : Easy to use unit testing library for PHP
– Twitto : A web framework in a tweet
– Twittee : A Dependency Injection Container in a tweet
– Pimple : A small PHP 5.3 dependency injection container
4. Fabien Potencier
• Read my technical blog: http://fabien.potencier.org/
• Follow me on Twitter: @fabpot
• Fork my code on Github: http://github.com/fabpot/
6. In most web applications, you need to manage the user preferences
– The user language
– Whether the user is authenticated or not
– The user credentials
– …
7. This can be done with a User object
– setLanguage(), getLanguage()
– setAuthenticated(), isAuthenticated()
– addCredential(), hasCredential()
– ...
8. The User information
need to be persisted
between HTTP requests
We use the PHP session for the Storage
9. class SessionStorage
{
function __construct($cookieName = 'PHP_SESS_ID')
{
session_name($cookieName);
session_start();
}
function set($key, $value)
{
$_SESSION[$key] = $value;
}
// ...
}
10. class User
{
protected $storage;
function __construct() Very hard to
{ customize
$this->storage = new SessionStorage();
}
function setLanguage($language)
{
$this->storage->set('language', $language);
}
// ...
} Very easy
to use
$user = new User();
11. class User
{
protected $storage; Very easy to
customize
function __construct($storage)
{
$this->storage = $storage;
}
}
$storage = new SessionStorage();
$user = new User($storage);
Slightly more
difficult to use
15. class User
{
protected $storage;
function __construct()
Hardcode it in the
{ User class
$this->storage = new SessionStorage('SESSION_ID');
}
function setLanguage($language)
{
$this->storage->set('language', $language);
}
// ...
}
$user = new User();
16. class User
{
protected $storage;
function __construct()
{
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);
}
}
Add a global
configuration?
define('STORAGE_SESSION_NAME', 'SESSION_ID');
$user = new User();
17. class User
{
protected $storage;
function __construct($sessionName)
{
$this->storage = new SessionStorage($sessionName);
}
}
$user = new User('SESSION_ID');
Configure via
User?
18. class User
{
protected $storage;
function __construct($storageOptions)
{
$this->storage = new SessionStorage($storageOptions
['session_name']);
$user = new User(
array('session_name' => 'SESSION_ID')
);
Configure with an
array?
19. I want to change the session storage implementation
Filesystem
MySQL
Memcached
…
20. class User
{
protected $storage;
function __construct() Use a global
{ registry object?
$this->storage = Registry::get('session_storage');
}
}
$storage = new SessionStorage();
Registry::set('session_storage', $storage);
$user = new User();
22. Instead of harcoding
the Storage dependency
inside the User class constructor
Inject the Storage dependency
in the User object
23. class User
{
protected $storage;
function __construct($storage)
{
$this->storage = $storage;
}
}
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
26. class User
{
protected $storage;
function __construct($storage)
{
$this->storage = $storage;
}
} Use a different
Storage engine
$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
28. class User
{
protected $storage;
function __construct($storage)
{
$this->storage = $storage;
}
} Configuration
is natural
$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
30. class User
{
protected $storage;
Add an interface
function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
}
}
interface SessionStorageInterface
{
function get($key);
function set($key, $value);
}
32. class User
{
protected $storage;
function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
}
}
Mock the Session
class SessionStorageForTests implements SessionStorageInterface
{
protected $data = array();
static function set($key, $value)
{
self::$data[$key] = $value;
}
}
33. 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
34. « Dependency Injection is where
components are given their dependencies
through their constructors, methods, or
directly into fields. »
http://www.picocontainer.org/injection.html
35. $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;
37. $request = new Request();
$response = new Response();
$storage = new FileSessionStorage('SESSION_ID');
$user = new User($storage);
$cache = new FileCache(
array('dir' => dirname(__FILE__).'/cache')
);
$routing = new Routing($cache);
38. class Application
{
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();
40. class Application
{
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();
41. We need a Container
Describes objects
and their dependencies
Instantiates and configures
objects on-demand
42. A container
SHOULD be able to manage
any PHP object (POPO)
The objects MUST not know
that they are managed
by a container
43. • Parameters
– The SessionStorageInterface implementation we want to use (the class name)
– The session name
• Objects
– SessionStorage
– User
• Dependencies
– User depends on a SessionStorageInterface implementation
46. class Container
{
protected $parameters = array();
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
}
public function getParameter($key)
{
return $this->parameters[$key];
}
}
47. Decoupling
$container = new Container(); Customization
$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);
Objects creation
48. class Container Using PHP
{ magic methods
protected $parameters = array();
public function __set($key, $value)
{
$this->parameters[$key] = $value;
}
public function __get($key)
{
return $this->parameters[$key];
}
}
49. Interface
is cleaner
$container = new Container();
$container->session_name = 'SESSION_ID';
$container->storage_class = 'SessionStorage';
$sessionStorage = new $container->storage_class($container->session_name);
$user = new User($sessionStorage);
51. We need a way to describe how to create objects,
without actually instantiating anything!
Anonymous functions to the rescue!
52. Anonymous Functions / Lambdas
A lambda is a function
defined on the fly
with no name
function () { echo 'Hello world!'; };
53. Anonymous Functions / Lambdas
A lambda can be stored
in a variable
$hello = function () { echo 'Hello world!'; };
54. Anonymous Functions / Lambdas
And then it can be used
as any other PHP callable
$hello();
call_user_func($hello);
55. Anonymous Functions / Lambdas
You can also pass a lambda
as an argument to a function or method
function foo(Closure $func)
{
$func();
}
foo($hello);
58. class Container
{
protected $parameters = array();
protected $objects = array();
public function __set($key, $value)
{
$this->parameters[$key] = $value;
}
public function __get($key) Store a lambda
{
return $this->parameters[$key]; able to create the
object on-demand
}
public function setService($key, Closure $service)
{
$this->objects[$key] = $service;
}
public function getService($key) Ask the closure to create
{
return $this->objects[$key]($this); th e object and pass the
}
} current Container
59. $container = new Container();
$container->session_name = 'SESSION_ID'; Description
$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);
});
Creating the User
is now as easy as before
$user = $container->getService('user');
60. class Container
{
protected $values = array(); Simplify the code
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];
}
}
}
61. $container = new Container();
Unified interface
$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;
68. A closure is a lambda
that remembers the context
of its creation…
69. class Article
{
public function __construct($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
$articles = array(
new Article('Title 1'),
new Article('Title 2'),
);
75. function asShared(Closure $lambda)
{
return function ($container) use ($lambda)
{
static $object;
if (is_null($object))
{
$object = $lambda($container);
}
return $object;
};
}
76. class Container
{
protected $values = array();
function __set($id, $value)
{
$this->values[$id] = $value;
}
function __get($id)
{ Error management
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];
}
}
}
77. class Container
{
protected $values = array();
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];
}
}
function asShared($callable)
{
40 LOC for a fully-
return function ($c) use ($callable)
{
static $object;
if (is_null($object))
{
$object = $callable($c);
featured container
}
return $object;
};
}
}
78. More about Dependency Injection
http://fabien.potencier.org/article/17/on-php-5-3-lambda-functions-and-closures
http://components.symfony-project.org/dependency-injection/
http://github.com/fabpot/pimple
http://twittee.org/
79. Twittee: A Container in a tweet
• Implementation does not use PHP 5.3
• Its usage needs PHP 5.3
class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}
twittee.org
80. Remember, most of the time,
you don’t need a Container
to use Dependency Injection
81. You can start to use and benefit from
Dependency Injection today
82. by implementing it
in your projects
by using externals libraries
that already use DI
without the need of a container
90. DI Container Hello World example
use SymfonyComponentsDependencyInjectionBuilder;
use SymfonyComponentsDependencyInjectionReference;
$container = new Builder();
$container->register('output', 'FancyOutput');
$container->
register('message', 'Message')->
setArguments(array(new Reference('output'), array('with_newline' => true)))
;
$container->message->say('Hello World!');
91. $message = $container->message;
Get the configuration for the message service
The Message constructor must be given an output service
Get the output object from the container
Create a Message object by passing the constructor arguments
92. $message = $container->message;
is roughly equivalent to
$output = new FancyOutput();
$message = new Message($output, array('with_newline' => true));!
93. $container = new Builder();
$container->register('output', 'FancyOutput');
$container->
register('message', 'Message')->
setArguments(array(new Reference('output'), array('with_newline' => true)))
;
$container->message->say('Hello World!'); PHP
<container xmlns="http://symfony-project.org/2.0/container">
<services> XML
<service id="output" class="FancyOutput" />
<service id="message" class="Message"> XML is validated
<argument type="service" id="output" /> against an XSD
<argument type="collection">
<argument key="with_newline">true</argument>
</argument>
</service>
</services>
</container>
$container = new Builder();
$loader = new XmlFileLoader($container);
$loader->load('services.xml');
98. Loaders & Dumpers
• IniFileLoader
• XmlFileLoader
• YamlFileLoader
• XmlDumper
• YamlDumper
Make your container
• PhpDumper VERY fast
• GraphvizDumper
99. use SymfonyComponentsDependencyInjectionBuilder;
use SymfonyComponentsDependencyInjectionReference;
use SymfonyComponentsDependencyInjectionDumperXmlDumper;
use SymfonyComponentsDependencyInjectionDumperYamlDumper;
use SymfonyComponentsDependencyInjectionDumperPhpDumper;
use SymfonyComponentsDependencyInjectionLoaderXmlFileLoader;
use SymfonyComponentsDependencyInjectionLoaderYamlFileLoader;
$container = new Builder();
$container->register('output', 'FancyOutput');
$container->
register('message', 'Message')->
setArguments(array(new Reference('output'), array('with_newline' => true)))
;
100. $dumper = new XmlDumper($container);
file_put_contents(__DIR__.'/container.xml', $dumper->dump());
$loader = new XmlFileLoader($container);
$loader->load(__DIR__.'/container.xml');
$dumper = new YamlDumper($container);
file_put_contents(__DIR__.'/container.yml', $dumper->dump());
$loader = new YamlFileLoader($container);
$loader->load(__DIR__.'/container.yml');
$dumper = new PhpDumper($container);
echo $dumper->dump();
101. use SymfonyComponentsDependencyInjectionContainer;
use SymfonyComponentsDependencyInjectionReference;
use SymfonyComponentsDependencyInjectionParameter;
class ProjectServiceContainer extends Container
{
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->getService('output'), array('with_newline' => true));
return $this->shared['message'] = $instance;
}
}