Professional Documents
Culture Documents
Fabien Potencier
Fabien Potencier
Serial entrepreneur
Developer by passion
Founder of Sensio
Creator and lead developer of Symfony
On Twitter @fabpot
On github http://www.github.com/fabpot
Blog http://fabien.potencier.org/
Dependency Injection
– setLanguage(), getLanguage()
– setAuthenticated(), isAuthenticated()
– addCredential(), hasCredential()
– ...
The User information
need to be persisted
between HTTP requests
// ...
}
class User
{
protected $storage;
function setLanguage($language)
{
$this->storage->set('language', $language);
}
// ...
} Very easy
to use
$user = new User();
class User
{
protected $storage; Very easy to
customize
function __construct($storage)
{
$this->storage = $storage;
}
}
Nothing more
Let’s understand why the first example
is not a good idea
I want to change the session cookie name
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);
}
// ...
}
function __construct()
{
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);
}
}
Add a global
configuration?
define('STORAGE_SESSION_NAME', 'SESSION_ID');
function __construct($sessionName)
{
$this->storage = new SessionStorage($sessionName);
}
}
function __construct($storageOptions)
{
$this->storage = new SessionStorage($storageOptions
['session_name']);
Filesystem
MySQL
Memcached
…
class User
{
protected $storage;
function __construct($storage)
{
$this->storage = $storage;
}
}
function __construct($storage)
{
$this->storage = $storage;
}
} Use a different
Storage engine
$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
Configuration becomes natural
class User
{
protected $storage;
function __construct($storage)
{
$this->storage = $storage;
}
} Configuration
is natural
$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
Wrap third-party classes (Interface / Adapter)
class User
{
protected $storage;
Add an interface
function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
}
}
interface SessionStorageInterface
{
function get($key);
// constructor injection
$user = new User($storage);
// setter injection
$user = new User();
$user->setStorage($storage);
// property injection
$user = new User();
$user->storage = $storage;
A slightly more complex
web example
$request = new Request();
$response = new Response();
Managing parameters
class Container
{
protected $parameters = array();
$class = $container->getParameter('storage_class');
$sessionStorage = new $class($container->getParameter('session_name'));
$user = new User($sessionStorage);
Objects creation
class Container Using PHP
{ magic methods
protected $parameters = array();
Managing objects
We need a way to describe how to create objects,
without actually instantiating anything!
A lambda is a function
defined on the fly
with no name
$hello();
call_user_func($hello);
Anonymous Functions / Lambdas
foo($hello);
Fonctions anonymes
$hello = function ($name) { echo 'Hello '.$name; };
$hello('Fabien');
call_user_func($hello, 'Fabien');
foo($hello, 'Fabien');
DI Container
Managing objects
class Container
{
protected $parameters = array();
protected $objects = array();
}
} current Container
$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);
});
function __get($id)
{
if (is_callable($this->values[$id]))
{
return $this->values[$id]($this);
}
else
{
return $this->values[$id];
}
}
}
$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;
DI Container
Scope
For some objects, like the user,
the container must always
return the same instance
spl_object_hash($container->user)
!==
spl_object_hash($container->user)
$container->user = function ($c)
{
static $user;
if (is_null($user))
{
$user = new User($c->storage);
}
return $user;
};
spl_object_hash($container->user)
===
spl_object_hash($container->user)
$container->user = $container->asShared(function ($c)
{
return new User($c->storage);
});
A closure is a lambda
that remembers the context
of its creation…
class Article
{
public function __construct($title)
{
$this->title = $title;
}
$articles = array(
new Article('Title 1'),
new Article('Title 2'),
);
$mapper = function ($article)
{
return $article->getTitle();
};
$method = 'getAuthor';
if (is_null($object))
{
$object = $lambda($container);
}
return $object;
};
}
class Container
{
protected $values = array();
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];
}
}
}
class Container
{
protected $values = array();
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)
{
if (is_null($object))
{
$object = $callable($c);
featured container
}
return $object;
};
}
}
I’m NOT advocating
the usage of lambdas everywhere
$container->register('output', 'FancyOutput');
$container->
register('message', 'Message')->
setArguments(array(
new sfServiceReference('output'),
array('with_newline' => true)
))
;
$container->message->say('Hello World!');
services:
output: { class: FancyOutput }
message:
class: Message
arguments:
- @output
- { with_newline: true }
<container xmlns="http://symfony-project.org/schema/dic/services">
<services>
<service id="output" class="FancyOutput" />
imports:
- { resource: parameters.yml }
- { resource: parameters.ini }
- { resource: services.xml }
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;
<zend:logger
priority="debug"
path="%kernel.root_dir%/logs/%kernel.environment%.log" />
<swift:mailer transport="gmail">
<swift:username>fabien.potencier</swift:username>
<swift:password>xxxxxx</swift:password>
</swift:mailer>
</container>
<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swift="http://www.symfony-project.org/schema/dic/swiftmailer"
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">
auto-completion
<doctrine:dbal dbname="dbname" username="root" password="" />
</container>
zend.logger:
level: debug
path: %kernel.root_dir%/logs/%kernel.environment%.log
doctrine.dbal:
dbname: dbname
username: root
password: ~
swift.mailer:
transport: gmail
username: fabien.potencier
password: xxxxxxx
Everything is converted by the extension
to plain services and parameters
no overhead
Loader::registerExtension(new SwiftMailerExtension());
Loader::registerExtension(new DoctrineExtension());
Loader::registerExtension(new ZendExtension());
$container->mailer->...
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/
Remember, most of the time,
you don’t need a Container
to use Dependency Injection
You can start to use and benefit from
Dependency Injection today
by implementing it
in your projects
Contact
Fabien Potencier
fabien.potencier at sensio.com
http://www.sensiolabs.com/
http://www.symfony-project.org/
http://fabien.potencier.org/