SlideShare a Scribd company logo
Dependency Injection
with PHP and PHP 5.3

Fabien Potencier
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
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
Fabien Potencier

•  Read my technical blog: http://fabien.potencier.org/

•  Follow me on Twitter: @fabpot

•  Fork my code on Github: http://github.com/fabpot/
Dependency Injection

A real world « web » example
In most web applications, you need to manage the user preferences

   –  The user language
   –  Whether the user is authenticated or not
   –  The user credentials
   –  …
This can be done with a User object

   –  setLanguage(), getLanguage()
   –  setAuthenticated(), isAuthenticated()
   –  addCredential(), hasCredential()
   –  ...
The User information
         need to be persisted
        between HTTP requests

We use the PHP session for the Storage
class SessionStorage
{
  function __construct($cookieName = 'PHP_SESS_ID')
  {
    session_name($cookieName);
    session_start();
  }

    function set($key, $value)
    {
      $_SESSION[$key] = $value;
    }

    // ...
}
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();
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
That’s Dependency Injection

      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);
    }

    // ...
}

$user = new User();
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();
class User
{
  protected $storage;

    function __construct($sessionName)
    {
      $this->storage = new SessionStorage($sessionName);
    }
}

$user = new User('SESSION_ID');
                                         Configure via
                                             User?
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?
I want to change the session storage implementation

                   Filesystem
                     MySQL
                   Memcached
                       …
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();
Now, the User depends on the Registry
Instead of harcoding
    the Storage dependency
inside the User class constructor

Inject the Storage dependency
       in the User object
class User
{
  protected $storage;

    function __construct($storage)
    {
      $this->storage = $storage;
    }
}

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
What are the advantages?
Use different Storage strategies
class User
{
  protected $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);

    function set($key, $value);
}
Mock the Storage object (for testing)
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;
    }
}
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
« Dependency Injection is where
   components are given their dependencies
    through their constructors, methods, or
             directly into fields. »
http://www.picocontainer.org/injection.html
$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;
A slightly more complex
web example
$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);
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();
Back to square 1
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();
We need a Container
     Describes objects
  and their dependencies

 Instantiates and configures
     objects on-demand
A container
SHOULD be able to manage
  any PHP object (POPO)


The objects MUST not know
  that they are managed
      by a container
•  Parameters
   –  The SessionStorageInterface implementation we want to use (the class name)
   –  The session name
•  Objects
   –  SessionStorage
   –  User
•  Dependencies
   –  User depends on a SessionStorageInterface implementation
Let’s build a simple container with PHP 5.3
DI Container

Managing parameters
class Container
{
  protected $parameters = array();

    public function setParameter($key, $value)
    {
      $this->parameters[$key] = $value;
    }

    public function getParameter($key)
    {
      return $this->parameters[$key];
    }
}
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
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];
    }
}
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);
DI Container

Managing objects
We need a way to describe how to create objects,
    without actually instantiating anything!

      Anonymous functions to the rescue!
Anonymous Functions / Lambdas


               A lambda is a function
                   defined on the fly
                    with no name

function () { echo 'Hello world!'; };
Anonymous Functions / Lambdas

                A lambda can be stored
                      in a variable

$hello = function () { echo 'Hello world!'; };
Anonymous Functions / Lambdas

                And then it can be used
                as any other PHP callable

$hello();


call_user_func($hello);
Anonymous Functions / Lambdas

                    You can also pass a lambda
               as an argument to a function or method

function foo(Closure $func)
{
  $func();
}

foo($hello);
Fonctions anonymes
$hello = function ($name) { echo 'Hello '.$name; };

$hello('Fabien');

call_user_func($hello, 'Fabien');

function foo(Closure $func, $name)
{
  $func($name);
}

foo($hello, 'Fabien');
DI Container

Managing objects
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
$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');
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];
      }
    }
}
$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;
  }

    public function getTitle()
    {
      return $this->title;
    }
}

$articles = array(
   new Article('Title 1'),
   new Article('Title 2'),
);
$mapper = function ($article)
{
   return $article->getTitle();
};

$titles = array_map($mapper, $articles);
$method = 'getTitle';

$mapper = function ($article) use($method)
{
   return $article->$method();
};

$method = 'getAuthor';

$titles = array_map($mapper, $articles);
$mapper = function ($method)
{
   return function ($article) use($method)
   {
      return $article->$method();
   };
};
$titles = array_map($mapper('getTitle'), $articles);


$authors = array_map($mapper('getAuthor'), $articles);
$container->user = $container->asShared(function ($c)
{
  return new User($c->storage);
});
function asShared(Closure $lambda)
{
  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)
    {
      $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];
        }
    }
}
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;
        };
    }
}
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/
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
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

  by using externals libraries
      that already use DI
without the need of a container
Symfony
Zend Framework
 ezComponents
    Doctrine
  Swift Mailer
      …
Questions?

My slides will be available on
slideshare.com/fabpot
symfony-live.com
              with Matthew Weier O’Pheinney




    I will reveal the first alpha release of Symfony 2.0!
symfony-live.com
             with Matthew Weier O’Pheinney




   … with Matthew Weier O'Phinney as a special guest
Sensio S.A.
    92-98, boulevard Victor Hugo
        92 115 Clichy Cedex
              FRANCE
       Tél. : +33 1 40 99 80 80

               Contact
           Fabien Potencier
    fabien.potencier at sensio.com




  http://www.sensiolabs.com/
http://www.symfony-project.org/
 http://fabien.potencier.org/
BONUS
Symfony Components
Dependency Injection
SymfonyComponentsDependencyInjection
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!');
$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
$message = $container->message;


                    is roughly equivalent to

                  $output = new FancyOutput();
$message = new Message($output, array('with_newline' => true));!
$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');
$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!');                                          PHP
services:
  output: { class: FancyOutput }
  message:                                                                         YAML
    class: Message
    arguments:
      - @output
      - { with_newline: true }

$container = new Builder();
$loader = new YamlFileLoader($container);
$loader->load('services.yml');
<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="output.class">FancyOutput</parameter>
    <parameter key="message.options" type="collection">
      <parameter key="with_newline">true</parameter>
    </parameter>
  </parameters>

  <services>
    <service id="output" class="%output.class%" />

    <service id="message" class="Message">
      <argument type="service" id="output" />
      <argument>%message.options%</argument>
    </service>
  </services>
</container>

$container = new Builder();
$loader = new XmlFileLoader($container);
$loader->load('services.xml');
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="config.xml" />
  </imports>

  <services>
    <service id="output" class="%output.class%" />
    <service id="message" class="Message">
      <argument type="service" id="output" />
      <argument>%message.options%</argument>
    </service>
  </services>
</container>

<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="output.class">FancyOutput</parameter>
    <parameter key="message.options" type="collection">
      <parameter key="with_newline">true</parameter>
    </parameter>
  </parameters>
</container>

$container = new Builder();
$loader = new FileXmlFileLoader($container);
$loader->load('services.xml');
<services>
 <import resource="config.yml" class="SymfonyComponentsDependencyInjection
LoaderYamlFileLoader" />

  <service id="output" class="%output.class%" />
  <service id="message" class="Message">
    <argument type="service" id="output" />
    <argument>%message.options%</argument>
  </service>
</services>

parameters:
  output.class: FancyOutput

  message.options: { with_newline: true }

$container = new Builder();
$loader = new XmlFileLoader($container);
$loader->load('services.xml');
Loaders & Dumpers
•  IniFileLoader
•  XmlFileLoader
•  YamlFileLoader

•    XmlDumper
•    YamlDumper
                    Make your container
•    PhpDumper      VERY fast
•    GraphvizDumper
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)))
;
$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();
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;
    }
}
use SymfonyComponentsDependencyInjectionDumperGraphvizDumper;

$dumper = new GraphvizDumper($container);
echo $dumper->dump();
digraph sc {
  ratio="compress"
  node [fontsize="11" fontname="Arial" shape="record"];
  edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"];

  node_output [label="outputnFancyOutputn", shape=record, fillcolor="#eeeeee", style="filled"];
  node_message [label="messagenMessagen", shape=record, fillcolor="#eeeeee", style="filled"];
  node_service_container [label="service_containernSymfonyComponentsDependencyInjectionBuilder
n", shape=record, fillcolor="#9999ff", style="filled"];
  node_message -> node_output [label="" style="filled"];
}
Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP and PHP 5.3

More Related Content

PPT
Internal and External Conflict
PPTX
LIFE-of-PI.pptx
PPTX
Space Exploration
PPT
Charte commerciale
PDF
Dependency Injection with PHP 5.3
PDF
Dependency Injection
PDF
Dependency Injection in Laravel
PPTX
Inversion of Control and Dependency Injection
Internal and External Conflict
LIFE-of-PI.pptx
Space Exploration
Charte commerciale
Dependency Injection with PHP 5.3
Dependency Injection
Dependency Injection in Laravel
Inversion of Control and Dependency Injection

Viewers also liked (6)

PPTX
Functional Dependency Injection in C#
PPTX
Dependency injection - the right way
PPTX
Inversion of Control - Introduction and Best Practice
PDF
Do you know Dependency Injection ?
PDF
You're Still Doing It Wrong
PPTX
IoC and Mapper in C#
Functional Dependency Injection in C#
Dependency injection - the right way
Inversion of Control - Introduction and Best Practice
Do you know Dependency Injection ?
You're Still Doing It Wrong
IoC and Mapper in C#
Ad

Similar to Dependency Injection with PHP and PHP 5.3 (20)

PDF
Dependency Injection IPC 201
PDF
Dependency injection - phpday 2010
PDF
Dependency Injection - ConFoo 2010
PDF
Dependency injection in PHP 5.3/5.4
PDF
Decouple Your Code For Reusability (International PHP Conference / IPC 2008)
PDF
Dependency injection-zendcon-2010
PDF
Beyond symfony 1.2 (Symfony Camp 2008)
PDF
Design patterns revisited with PHP 5.3
KEY
PDF
Dependency injection in Drupal 8
PDF
Dependency Injection
PDF
14 Dependency Injection #burningkeyboards
KEY
IoC with PHP
KEY
CICONF 2012 - Don't Make Me Read Your Mind
PDF
Lithium: The Framework for People Who Hate Frameworks
PDF
Maintaining legacy applications
PPTX
Adding Dependency Injection to Legacy Applications
PDF
Migrating to dependency injection
PDF
Zend Server Data Caching
PDF
Dutch php conference_apc_mem2010
Dependency Injection IPC 201
Dependency injection - phpday 2010
Dependency Injection - ConFoo 2010
Dependency injection in PHP 5.3/5.4
Decouple Your Code For Reusability (International PHP Conference / IPC 2008)
Dependency injection-zendcon-2010
Beyond symfony 1.2 (Symfony Camp 2008)
Design patterns revisited with PHP 5.3
Dependency injection in Drupal 8
Dependency Injection
14 Dependency Injection #burningkeyboards
IoC with PHP
CICONF 2012 - Don't Make Me Read Your Mind
Lithium: The Framework for People Who Hate Frameworks
Maintaining legacy applications
Adding Dependency Injection to Legacy Applications
Migrating to dependency injection
Zend Server Data Caching
Dutch php conference_apc_mem2010
Ad

More from Fabien Potencier (20)

PDF
PDF
Look beyond PHP
PDF
Caching on the Edge
PDF
The state of Symfony2 - SymfonyDay 2010
PDF
PhpBB meets Symfony2
PDF
Symfony2 - WebExpo 2010
PDF
Symfony2 - WebExpo 2010
PDF
Symfony2 - OSIDays 2010
PDF
Caching on the Edge with Symfony2
PDF
Unit and Functional Testing with Symfony2
PDF
News of the Symfony2 World
PDF
Symfony Components
PDF
PHP 5.3 in practice
PDF
Symfony2 revealed
PDF
Symfony Components 2.0 on PHP 5.3
PDF
Playing With PHP 5.3
PDF
Symfony 2.0 on PHP 5.3
PDF
Symfony2 San Francisco Meetup 2009
PDF
Symfony And Zend Framework Together 2009
PDF
Twig, the flexible, fast, and secure template language for PHP
Look beyond PHP
Caching on the Edge
The state of Symfony2 - SymfonyDay 2010
PhpBB meets Symfony2
Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010
Symfony2 - OSIDays 2010
Caching on the Edge with Symfony2
Unit and Functional Testing with Symfony2
News of the Symfony2 World
Symfony Components
PHP 5.3 in practice
Symfony2 revealed
Symfony Components 2.0 on PHP 5.3
Playing With PHP 5.3
Symfony 2.0 on PHP 5.3
Symfony2 San Francisco Meetup 2009
Symfony And Zend Framework Together 2009
Twig, the flexible, fast, and secure template language for PHP

Recently uploaded (20)

PDF
Encapsulation theory and applications.pdf
PDF
KodekX | Application Modernization Development
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Cloud computing and distributed systems.
PDF
Approach and Philosophy of On baking technology
PDF
NewMind AI Monthly Chronicles - July 2025
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Electronic commerce courselecture one. Pdf
PDF
Machine learning based COVID-19 study performance prediction
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PPT
Teaching material agriculture food technology
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Encapsulation theory and applications.pdf
KodekX | Application Modernization Development
20250228 LYD VKU AI Blended-Learning.pptx
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Reach Out and Touch Someone: Haptics and Empathic Computing
Cloud computing and distributed systems.
Approach and Philosophy of On baking technology
NewMind AI Monthly Chronicles - July 2025
Unlocking AI with Model Context Protocol (MCP)
Electronic commerce courselecture one. Pdf
Machine learning based COVID-19 study performance prediction
The Rise and Fall of 3GPP – Time for a Sabbatical?
Building Integrated photovoltaic BIPV_UPV.pdf
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Teaching material agriculture food technology
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Agricultural_Statistics_at_a_Glance_2022_0.pdf

Dependency Injection with PHP and PHP 5.3

  • 1. Dependency Injection with PHP and PHP 5.3 Fabien Potencier
  • 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/
  • 5. Dependency Injection A real world « web » example
  • 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
  • 13. Let’s understand why the first example is not a good idea
  • 14. I want to change the session cookie name
  • 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();
  • 21. Now, the User depends on the Registry
  • 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);
  • 24. What are the advantages?
  • 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);
  • 29. Wrap third-party classes (Interface / Adapter)
  • 30. class User { protected $storage; Add an interface function __construct(SessionStorageInterface $storage) { $this->storage = $storage; } } interface SessionStorageInterface { function get($key); function set($key, $value); }
  • 31. Mock the Storage object (for testing)
  • 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;
  • 36. A slightly more complex web example
  • 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
  • 44. Let’s build a simple container with PHP 5.3
  • 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);
  • 56. Fonctions anonymes $hello = function ($name) { echo 'Hello '.$name; }; $hello('Fabien'); call_user_func($hello, 'Fabien'); function foo(Closure $func, $name) { $func($name); } foo($hello, 'Fabien');
  • 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;
  • 63. For some objects, like the user, the container must always return the same instance
  • 64. spl_object_hash($container->user) !== spl_object_hash($container->user)
  • 65. $container->user = function ($c) { static $user; if (is_null($user)) { $user = new User($c->storage); } return $user; };
  • 66. spl_object_hash($container->user) === spl_object_hash($container->user)
  • 67. $container->user = $container->asShared(function ($c) { return new User($c->storage); });
  • 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'), );
  • 70. $mapper = function ($article) { return $article->getTitle(); }; $titles = array_map($mapper, $articles);
  • 71. $method = 'getTitle'; $mapper = function ($article) use($method) { return $article->$method(); }; $method = 'getAuthor'; $titles = array_map($mapper, $articles);
  • 72. $mapper = function ($method) { return function ($article) use($method) { return $article->$method(); }; };
  • 73. $titles = array_map($mapper('getTitle'), $articles); $authors = array_map($mapper('getAuthor'), $articles);
  • 74. $container->user = $container->asShared(function ($c) { return new User($c->storage); });
  • 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
  • 83. Symfony Zend Framework ezComponents Doctrine Swift Mailer …
  • 84. Questions? My slides will be available on slideshare.com/fabpot
  • 85. symfony-live.com with Matthew Weier O’Pheinney I will reveal the first alpha release of Symfony 2.0!
  • 86. symfony-live.com with Matthew Weier O’Pheinney … with Matthew Weier O'Phinney as a special guest
  • 87. Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tél. : +33 1 40 99 80 80 Contact Fabien Potencier fabien.potencier at sensio.com http://www.sensiolabs.com/ http://www.symfony-project.org/ http://fabien.potencier.org/
  • 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');
  • 94. $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!'); PHP services: output: { class: FancyOutput } message: YAML class: Message arguments: - @output - { with_newline: true } $container = new Builder(); $loader = new YamlFileLoader($container); $loader->load('services.yml');
  • 95. <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="output.class">FancyOutput</parameter> <parameter key="message.options" type="collection"> <parameter key="with_newline">true</parameter> </parameter> </parameters> <services> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> </container> $container = new Builder(); $loader = new XmlFileLoader($container); $loader->load('services.xml');
  • 96. <container xmlns="http://symfony-project.org/2.0/container"> <imports> <import resource="config.xml" /> </imports> <services> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> </container> <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="output.class">FancyOutput</parameter> <parameter key="message.options" type="collection"> <parameter key="with_newline">true</parameter> </parameter> </parameters> </container> $container = new Builder(); $loader = new FileXmlFileLoader($container); $loader->load('services.xml');
  • 97. <services> <import resource="config.yml" class="SymfonyComponentsDependencyInjection LoaderYamlFileLoader" /> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> parameters: output.class: FancyOutput message.options: { with_newline: true } $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; } }
  • 102. use SymfonyComponentsDependencyInjectionDumperGraphvizDumper; $dumper = new GraphvizDumper($container); echo $dumper->dump();
  • 103. digraph sc { ratio="compress" node [fontsize="11" fontname="Arial" shape="record"]; edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; node_output [label="outputnFancyOutputn", shape=record, fillcolor="#eeeeee", style="filled"]; node_message [label="messagenMessagen", shape=record, fillcolor="#eeeeee", style="filled"]; node_service_container [label="service_containernSymfonyComponentsDependencyInjectionBuilder n", shape=record, fillcolor="#9999ff", style="filled"]; node_message -> node_output [label="" style="filled"]; }