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. I’m NOT advocating
the usage of lambdas everywhere
This presentation is about
showing how they work
on practical examples