SlideShare a Scribd company logo
Decouple your PHP code
    for reusability
       Fabien Potencier
Who am I?
•  Founder of Sensio
  –  Web Agency
  –  Since 1998
  –  About 50 people
  –  Open-Source Specialists
  –  Big corporate customers


•  Creator and lead developer of symfony
  –  PHP framework
Coupling?



Coupling is the degree to which
 each program module relies on
 each one of the other modules.

                 http://en.wikipedia.org/wiki/Coupling_(computer_science)
Low Coupling



With low coupling, a change in one
 module will not require a change
 in the implementation of another
             module.
Low Coupling



A module can interact with another
   module but through a stable
             interface
Why does it matter?
Testability



The ability to instantiate a class
     in a test demonstrates
     the level of decoupling
           of your code
Readability



 Code spends more time
being read and maintained
    than being created
Maintainability


A change "here"
 must not cause
a problem "there"
Extensibility



Smaller loosely coupled modules
         makes your code
         easier to extend
Reusability
The programming world
  is evolving fast… very fast

But the ultimate goal of these
      changes is the same
Build better tools
    to avoid reinventing
         the wheel
Build on existing code / work / libraries
Specialize for your specific needs
That’s why Open-Source wins
Better tools are possible because
we base our work on existing work

  We learn from other projects

    We can adopt their ideas

    We can make them better

 This is the process of evolution
Reusability

     Configuration
Customization / Extension
Documentation / Support
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()
    {
      $this->storage = new SessionStorage();
    }

    function setLanguage($language)
    {
      $this->storage->set('language', $language);
    }

    // ...
}

$user = new User();
I want to change the session cookie name
class User
{
  protected $storage;                   Hardcode it in
                                        the User class
    function __construct()
    {
      $this->storage = new SessionStorage('SESSION_ID');
    }

    function setLanguage($language)
    {
      $this->storage->set('language', $language);
    }

    // ...
}

$user = new User();
class User
{
  protected $storage;                           Add a global
                                               configuration?
    function __construct()
    {
        $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
    }
}



define('STORAGE_SESSION_NAME', 'SESSION_ID');

$user = new User();
Configure via
class User
                                           User?
{
  protected $storage;

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

$user = new User('SESSION_ID');
Configure with
class User                                an array
{
  protected $storage;

  function __construct($storageOptions)
  {
    $this->storage = new
 SessionStorage($storageOptions['session_name']);

$user = new User(
   array('session_name' => 'SESSION_ID')
);
I want to change the session storage engine

                Filesystem
                  MySQL
                  SQLite
                     …
Use a global
class User                             registry object?
{
  protected $storage;

    function __construct()
    {
      $this->storage = Registry::get('session_storage');
    }
}

$storage = new SessionStorage();
Registry::set('session_storage', $storage);
$user = new User();
The User now depends on the Registry
Instead of harcoding
  the Storage dependency
      in the User class

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

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

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
Use different Storage strategies
class User
{
  protected $storage;

    function __construct($storage)   Use a different
    {                                Storage engine
      $this->storage = $storage;
    }
}

$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
Configuration becomes natural
class User
{
  protected $storage;

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

$storage = new MySQLSessionStorage('SESSION_ID');
$user = new User($storage);
Wrap third-party classes (Interface / Adapter)
class User
{
  protected $storage;

  function __construct(ISessionStorage $storage)
  {
    $this->storage = $storage;
  }                                      Add an
}                                       interface

interface ISessionStorage
{
  function get($key);

    function set($key, $value);
}
Mock the Storage object (for testing)
class User
{
  protected $storage;

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

class SessionStorageForTests implements ISessionStorage
{
  protected $data
  function set($key, $value)
  {                                        Mock the
    self::$data[$key] = $value;             Session
  }
}
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
That’s Dependency Injection

      Nothing more
« 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 WebRequest();
$response = new WebResponse();

$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()
  {
    $request = new WebRequest();
    $response = new WebResponse();

        $storage = new FileSessionStorage('SESSION_ID');
        $user = new User($storage);

        $cache = new FileCache(
           array('dir' => dirname(__FILE__).'/cache')
        );
        $routing = new Routing($cache);
    }
}

$application = new Application();
We need a Container
           Objects
and relationships description

       Configuration

        Instantiation
Service
                                  description




$storageDef = new ServiceDefinition(
   'FileSessionStorage'
);




                     Class name
$storageDef = new ServiceDefinition(
   'FileSessionStorage',
   array('SESSION_ID')
);


        Arguments to
         pass to the
         constructor
$container = new ServiceContainer(array(
  'storage' => $storageDef,
));



      Each object has a
       unique identifier
$userDef = new ServiceDefinition(‘User’,
   array(new ServiceReference('storage'))
);



                                 Reference to
                                another object
$storageDef = new ServiceDefinition(
   ‘FileSessionStorage’,
   array(‘SESSION_ID’)
);

$userDef = new ServiceDefinition(‘User’,
   array(new ServiceReference(‘storage’))
);

$container = new ServiceContainer(array(
  'storage' => $storageDef,
  'user'    => $userDef,
));
$user = $container->getService('user');



         Get the configuration for the user object

   The User constructor must be given a storage object

        Get the storage object from the container

Create a User object by passing the constructor arguments
$user = $container->getService('user');



          is roughly equivalent to


$storage = new SessionStorage('SESSION_ID');
         $user = new User($storage);
A container
      is able to manage
      any object (POPO)


The objects do not need to know
       they are managed
         by a container
Implementation tips
public function getService ($id)
{
  $def = $this->definitions[$id];

    $r = new ReflectionClass($def->getClass());

    if (is_null($r->getConstructor()))
    {
      return $r->newInstance();
    }

    $args = $def->getArguments();
    $args = $this->evaluateArguments($args);

    return $r->newInstanceArgs($args);
}
public function evaluateArguments($arg)
{
  if (is_array($arg))
  {
    return array_map(
       array($this, 'evaluateArguments'), $arg
    );
  }

    if (
      is_object($arg)
      && $arg instanceof ServiceReference
    )
    {
      return $this->getService($arg);
    }

    return $arg;
}
Towards a real implementation
Scope
Each time you get a service:
 Do you want a new object
  or
 Does the container must return the same object?

$userDef->setGlobal(true);

$feedReaderDef->setGlobal(false);
Service definition
$container = new ServiceContainer(array(               PHP
  'storage' => new ServiceDefinition('FileSessionStorage'),
  'user'    => new ServiceDefinition('User',
                     array(new ServiceReference('storage'))
                  ),
));



<service id="storage" class="FileSessionStorage" />    XML
<service id="user" class="User">
  <constructor_arg type="service" id="storage" />
</service>
Service configuration
                                     Configuration is
<parameter key="storage.class">      decoupled from
  SQLiteSessionStorage                the definition
</parameter>
<parameter key="storage.session_name">
  SESSION_ID
</parameter>


<service id="storage" class="%storage.class%">
  <constructor_arg>
    %storage.session_name%
  </constructor_arg>
</service>
Service configuration


[storage]
  class = SQLiteSessionStorage
  session_name = SESSION_ID


<service id="storage" class="%storage.class%">
  <constructor_arg>
    %storage.session_name%
  </constructor_arg>
</service>
// Access parameters

$sessionName = $container['storage.session_name'];



// Access services

$user = $container->user;
Implementations in PHP
•  Crafty: http://phpcrafty.sourceforge.net/

•  Garden: http://garden.tigris.org/
                            spring
•  Stubbles: http://www.stubbles.net/wiki/Docs/IOC
                                       Google Guice


•  symfony 2 will have its dependency injection
   framework                            spring
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
       …
Let’s add some i18n fun
class User
{
  protected $storage, $i18n;

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

 function setLanguage($language)
 {
   $this->storage->set('language', $language);

     $this->i18n->setLanguage($language);
 }

  // ...
•  It makes sense that the User class depends on a
    Storage class
  –  We can’t use the User object without a Storage


•  It does not make sense to tie the User class to
    the I18n class
  –  We want to be able to use the User with non
     internationalized websites
class User
{
  protected $storage, $i18n;

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

 function setLanguage($language)
 {
   $this->storage->set('language', $language);

     if (!is_null($this->i18n))
     {
       $this->i18n->setLanguage($language);
     }
 }
class User
{
  protected $storage;

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

    function setI18n($i18n)
    {
      $this->i18n = $i18n;
    }

    function setLanguage($language)
    {
      $this->storage->set('language', $language);

        if (!is_null($this->i18n))
        {
          $this->i18n->setLanguage($language);
        }
    }
}
The Observer pattern
•  The Observer pattern is a way to allow changes in
   an object to update many other objects

•  It is a powerful mechanism to extend applications
    without having to change the object itself

•  Associated words: hook, listener, event, subject,
   …
•  Used by a lot of applications for:
        –  Plugins (Wordpress, …)
        –  Bridges between applications
                •  As a master application:
                        –  Gallery2 (90% events / hooks for user / group create / update / delete, logout,
                           login, ..missing: configuration changes)
                        –  Xaraya (100% events / hooks for user / group create / update / delete,
                           configuration changes, logout, login, ..)
                        –  Wordpress (90% events / hooks for user / update / delete, rewrites, logout,
                           login, ..)
                        –  Drupal (80% maybe?)
                        –  Joomla (100% joomla 1.5; login, logout, create, update, delete user, block,
                           activation, system before and after start)Typo3 (50% maybe?, e.g. no unified create
                           user event / hook)
                •  As a slave application:
                        –  Gallery 2
                        –  Phorum.org
http://codex.gallery2.org/Gallery2:Embedding:Event-Based_Loose-Coupled_Integration
Implementations in PHP
•  PEAR_Dispatcher
  –  http://pear.php.net/package/Event_Dispatcher

•  symfony implementation
  –  http://svn.symfony-project.com/branches/1.1/lib/event/
  –  http://www.symfony-project.org/book/1_1/17-Extending-Symfony
  –  Based on the Cocoa notification center
     •  Simple and powerful
     •  Decoupled from symfony
     •  Simple to use
$i18n = new I18n();
$dispatcher = new sfEventDispatcher();
$listener = array($i18n, 'listenToChangeCultureEvent');
$dispatcher->connect('user.change_language', $listener);

$storage = new FileSessionStorage();
$user = new User($dispatcher, $storage);
class User
{
  protected $storage, $dispatcher;

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

 function setLanguage($language)
 {
   $this->storage->set('language', $language);

     $event = new sfEvent(
        $this,
        'user.change_language',
        array('language' => $language)
     );

     $this->dispatcher->notify($event);
 }
Notifiers          Dispatcher             Listeners

                                                 I18n listens
1                                          to user.change_culture

       User notifies           Calls           I18n callback
2   user.change_culture    all listeners          is called
The User object knows nothing about the I18n one
The I18n object knows nothing about the User one

They communicate through the Dispatcher object

Any class can listen to the ‘user.change_culture’
  event and acts accordingly
Notifiers          Dispatcher             Listeners

                                                 I18n listens
1                                          to user.change_culture
                                              Your class listens
                                           to user.change_culture

       User notifies           Calls           I18n callback
2   user.change_culture    all listeners          is called
                                             Your class callback
                                                  is called
Notifiers          Dispatcher




   User notifies       Calls
user.change_culture   nothing




                            Very small
                             overhead
In this example, the implementation is very simple
  No interface to implement
  No need to create an event class for each event
  An event is just
    a unique identifier (user.change_culture)
   some conventions (parameter names)
Advantages
  very simple to use
  easy to add new arguments to the listener
  very fast
  very easy to add a new event, just notify it with a
   unique name
Disadvantages
 the contract between the listener and the notifier
   is quite loose
Implementation
function connect($name, $listener)
{
  if (!isset($this->listeners[$name]))
  {
    $this->listeners[$name] = array();
  }

    $this->listeners[$name][] = $listener;
}

function notify(sfEvent $event)
{
  if (!isset($this->listeners[$name]))
  {
    foreach ($this->listeners[$event->getName()] as $listener)
    {
      call_user_func($listener, $event);
    }
  }
}
•  3 types of notification
  –  Notify : all listeners are called in turn, no feedback to
     the notifier
     •  Logging, …
  –  Notify Until : all listeners are called until one has
     « processed » the event. The listener that has
     processed the event can return something to the
     caller
     •  Exceptions, …
  –  Filter : Each listener filter a given value and can
     change it. The filtered value is returned to the caller
     •  HTML content, …
Questions?

            Contact
       Fabien Potencier
 fabien.potencier@sensio.com



  http://www.sensiolabs.com/
http://www.symfony-project.org/

More Related Content

PPTX
Javascript Basics
PDF
eServices-Chp4: ESB
DOC
jdbc document
PDF
Java API for XML Web Services (JAX-WS)
PPT
Jsp/Servlet
PPT
Using Java to implement SOAP Web Services: JAX-WS
PPTX
Django app deployment in Azure By Saurabh Agarwal
PDF
React js t2 - jsx
Javascript Basics
eServices-Chp4: ESB
jdbc document
Java API for XML Web Services (JAX-WS)
Jsp/Servlet
Using Java to implement SOAP Web Services: JAX-WS
Django app deployment in Azure By Saurabh Agarwal
React js t2 - jsx

Viewers also liked (17)

PDF
Decoupling Your HTML, CSS & JavaScript
PDF
C4DM Seminar 2016-07-12: Brecht De Man
PPT
Web Services: Encapsulation, Reusability, and Simplicity
KEY
Ciklum Odessa PHP Saturday - Dependency Injection
PPTX
Dependency Injection. Как сказать всё, не говоря ничего. Кожевников Дмитрий. ...
PDF
Dependency Injection Pattern in JavaScript, Speakers' Corner by Evgeny Dmitri...
PDF
Dependency Injection in PHP
PDF
Measuring maintainability; software metrics explained
PDF
Dependency Injection with PHP 5.3
PPT
Тестирование безопасности: PHP инъекция
PDF
What Makes Great Infographics
PDF
Masters of SlideShare
PDF
STOP! VIEW THIS! 10-Step Checklist When Uploading to Slideshare
PDF
You Suck At PowerPoint!
PDF
10 Ways to Win at SlideShare SEO & Presentation Optimization
PDF
How To Get More From SlideShare - Super-Simple Tips For Content Marketing
PDF
How to Make Awesome SlideShares: Tips & Tricks
Decoupling Your HTML, CSS & JavaScript
C4DM Seminar 2016-07-12: Brecht De Man
Web Services: Encapsulation, Reusability, and Simplicity
Ciklum Odessa PHP Saturday - Dependency Injection
Dependency Injection. Как сказать всё, не говоря ничего. Кожевников Дмитрий. ...
Dependency Injection Pattern in JavaScript, Speakers' Corner by Evgeny Dmitri...
Dependency Injection in PHP
Measuring maintainability; software metrics explained
Dependency Injection with PHP 5.3
Тестирование безопасности: PHP инъекция
What Makes Great Infographics
Masters of SlideShare
STOP! VIEW THIS! 10-Step Checklist When Uploading to Slideshare
You Suck At PowerPoint!
10 Ways to Win at SlideShare SEO & Presentation Optimization
How To Get More From SlideShare - Super-Simple Tips For Content Marketing
How to Make Awesome SlideShares: Tips & Tricks
Ad

Similar to Decouple Your Code For Reusability (International PHP Conference / IPC 2008) (20)

PDF
Dependency Injection with PHP and PHP 5.3
PDF
Dependency injection in PHP 5.3/5.4
PDF
Dependency Injection IPC 201
PDF
Dependency injection - phpday 2010
PDF
Dependency Injection
PDF
Dependency Injection - ConFoo 2010
PDF
Dependency injection-zendcon-2010
PDF
Beyond symfony 1.2 (Symfony Camp 2008)
PDF
Dependency injection in Drupal 8
KEY
PDF
Design patterns revisited with PHP 5.3
PDF
Lithium: The Framework for People Who Hate Frameworks
KEY
IoC with PHP
PDF
Dependency Injection
KEY
CICONF 2012 - Don't Make Me Read Your Mind
PDF
Maintaining legacy applications
PDF
14 Dependency Injection #burningkeyboards
PDF
Symfony2 from the Trenches
PDF
Migrating to dependency injection
PPTX
Adding Dependency Injection to Legacy Applications
Dependency Injection with PHP and PHP 5.3
Dependency injection in PHP 5.3/5.4
Dependency Injection IPC 201
Dependency injection - phpday 2010
Dependency Injection
Dependency Injection - ConFoo 2010
Dependency injection-zendcon-2010
Beyond symfony 1.2 (Symfony Camp 2008)
Dependency injection in Drupal 8
Design patterns revisited with PHP 5.3
Lithium: The Framework for People Who Hate Frameworks
IoC with PHP
Dependency Injection
CICONF 2012 - Don't Make Me Read Your Mind
Maintaining legacy applications
14 Dependency Injection #burningkeyboards
Symfony2 from the Trenches
Migrating to dependency injection
Adding Dependency Injection to Legacy Applications
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
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
KodekX | Application Modernization Development
PPTX
breach-and-attack-simulation-cybersecurity-india-chennai-defenderrabbit-2025....
PDF
Approach and Philosophy of On baking technology
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PPTX
Cloud computing and distributed systems.
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
GDG Cloud Iasi [PUBLIC] Florian Blaga - Unveiling the Evolution of Cybersecur...
PDF
Advanced Soft Computing BINUS July 2025.pdf
PPTX
Big Data Technologies - Introduction.pptx
PDF
Electronic commerce courselecture one. Pdf
PDF
NewMind AI Weekly Chronicles - August'25 Week I
Mobile App Security Testing_ A Comprehensive Guide.pdf
KodekX | Application Modernization Development
breach-and-attack-simulation-cybersecurity-india-chennai-defenderrabbit-2025....
Approach and Philosophy of On baking technology
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
Diabetes mellitus diagnosis method based random forest with bat algorithm
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Spectral efficient network and resource selection model in 5G networks
Chapter 3 Spatial Domain Image Processing.pdf
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Cloud computing and distributed systems.
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
GDG Cloud Iasi [PUBLIC] Florian Blaga - Unveiling the Evolution of Cybersecur...
Advanced Soft Computing BINUS July 2025.pdf
Big Data Technologies - Introduction.pptx
Electronic commerce courselecture one. Pdf
NewMind AI Weekly Chronicles - August'25 Week I

Decouple Your Code For Reusability (International PHP Conference / IPC 2008)

  • 1. Decouple your PHP code for reusability Fabien Potencier
  • 2. Who am I? •  Founder of Sensio –  Web Agency –  Since 1998 –  About 50 people –  Open-Source Specialists –  Big corporate customers •  Creator and lead developer of symfony –  PHP framework
  • 3. Coupling? Coupling is the degree to which each program module relies on each one of the other modules. http://en.wikipedia.org/wiki/Coupling_(computer_science)
  • 4. Low Coupling With low coupling, a change in one module will not require a change in the implementation of another module.
  • 5. Low Coupling A module can interact with another module but through a stable interface
  • 6. Why does it matter?
  • 7. Testability The ability to instantiate a class in a test demonstrates the level of decoupling of your code
  • 8. Readability Code spends more time being read and maintained than being created
  • 9. Maintainability A change "here" must not cause a problem "there"
  • 10. Extensibility Smaller loosely coupled modules makes your code easier to extend
  • 12. The programming world is evolving fast… very fast But the ultimate goal of these changes is the same
  • 13. Build better tools to avoid reinventing the wheel Build on existing code / work / libraries Specialize for your specific needs That’s why Open-Source wins
  • 14. Better tools are possible because we base our work on existing work We learn from other projects We can adopt their ideas We can make them better This is the process of evolution
  • 15. Reusability Configuration Customization / Extension Documentation / Support
  • 17. A real world « web » example
  • 18. In most web applications, you need to manage the user preferences –  The user language –  Whether the user is authenticated or not –  The user credentials –  …
  • 19. This can be done with a User object – setLanguage(), getLanguage() – setAuthenticated(), isAuthenticated() – addCredential(), hasCredential() – ...
  • 20. The User information need to be persisted between HTTP requests We use the PHP session for the Storage
  • 21. class SessionStorage { function __construct($cookieName = 'PHP_SESS_ID') { session_name($cookieName); session_start(); } function set($key, $value) { $_SESSION[$key] = $value; } // ... }
  • 22. class User { protected $storage; function __construct() { $this->storage = new SessionStorage(); } function setLanguage($language) { $this->storage->set('language', $language); } // ... } $user = new User();
  • 23. I want to change the session cookie name
  • 24. class User { protected $storage; Hardcode it in the User class function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } function setLanguage($language) { $this->storage->set('language', $language); } // ... } $user = new User();
  • 25. class User { protected $storage; Add a global configuration? function __construct() { $this->storage = new SessionStorage(STORAGE_SESSION_NAME); } } define('STORAGE_SESSION_NAME', 'SESSION_ID'); $user = new User();
  • 26. Configure via class User User? { protected $storage; function __construct($sessionName) { $this->storage = new SessionStorage($sessionName); } } $user = new User('SESSION_ID');
  • 27. Configure with class User an array { protected $storage; function __construct($storageOptions) { $this->storage = new SessionStorage($storageOptions['session_name']); $user = new User( array('session_name' => 'SESSION_ID') );
  • 28. I want to change the session storage engine Filesystem MySQL SQLite …
  • 29. Use a global class User registry object? { protected $storage; function __construct() { $this->storage = Registry::get('session_storage'); } } $storage = new SessionStorage(); Registry::set('session_storage', $storage); $user = new User();
  • 30. The User now depends on the Registry
  • 31. Instead of harcoding the Storage dependency in the User class Inject the Storage dependency in the User object
  • 32. Constructor class User argument { protected $storage; function __construct($storage) { $this->storage = $storage; } } $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
  • 33. Use different Storage strategies
  • 34. class User { protected $storage; function __construct($storage) Use a different { Storage engine $this->storage = $storage; } } $storage = new MySQLSessionStorage('SESSION_ID'); $user = new User($storage);
  • 36. class User { protected $storage; function __construct($storage) Configuration { is natural $this->storage = $storage; } } $storage = new MySQLSessionStorage('SESSION_ID'); $user = new User($storage);
  • 37. Wrap third-party classes (Interface / Adapter)
  • 38. class User { protected $storage; function __construct(ISessionStorage $storage) { $this->storage = $storage; } Add an } interface interface ISessionStorage { function get($key); function set($key, $value); }
  • 39. Mock the Storage object (for testing)
  • 40. class User { protected $storage; function __construct(ISessionStorage $storage) { $this->storage = $storage; } } class SessionStorageForTests implements ISessionStorage { protected $data function set($key, $value) { Mock the self::$data[$key] = $value; Session } }
  • 41. 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
  • 43. « Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields. » http://www.picocontainer.org/injection.html
  • 44. $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;
  • 45. A slightly more complex web example
  • 46. $request = new WebRequest(); $response = new WebResponse(); $storage = new FileSessionStorage('SESSION_ID'); $user = new User($storage); $cache = new FileCache( array('dir' => dirname(__FILE__).'/cache') ); $routing = new Routing($cache);
  • 47. 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();
  • 49. class Application { function __construct() { $request = new WebRequest(); $response = new WebResponse(); $storage = new FileSessionStorage('SESSION_ID'); $user = new User($storage); $cache = new FileCache( array('dir' => dirname(__FILE__).'/cache') ); $routing = new Routing($cache); } } $application = new Application();
  • 50. We need a Container Objects and relationships description Configuration Instantiation
  • 51. Service description $storageDef = new ServiceDefinition( 'FileSessionStorage' ); Class name
  • 52. $storageDef = new ServiceDefinition( 'FileSessionStorage', array('SESSION_ID') ); Arguments to pass to the constructor
  • 53. $container = new ServiceContainer(array( 'storage' => $storageDef, )); Each object has a unique identifier
  • 54. $userDef = new ServiceDefinition(‘User’, array(new ServiceReference('storage')) ); Reference to another object
  • 55. $storageDef = new ServiceDefinition( ‘FileSessionStorage’, array(‘SESSION_ID’) ); $userDef = new ServiceDefinition(‘User’, array(new ServiceReference(‘storage’)) ); $container = new ServiceContainer(array( 'storage' => $storageDef, 'user' => $userDef, ));
  • 56. $user = $container->getService('user'); Get the configuration for the user object The User constructor must be given a storage object Get the storage object from the container Create a User object by passing the constructor arguments
  • 57. $user = $container->getService('user'); is roughly equivalent to $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
  • 58. A container is able to manage any object (POPO) The objects do not need to know they are managed by a container
  • 60. public function getService ($id) { $def = $this->definitions[$id]; $r = new ReflectionClass($def->getClass()); if (is_null($r->getConstructor())) { return $r->newInstance(); } $args = $def->getArguments(); $args = $this->evaluateArguments($args); return $r->newInstanceArgs($args); }
  • 61. public function evaluateArguments($arg) { if (is_array($arg)) { return array_map( array($this, 'evaluateArguments'), $arg ); } if ( is_object($arg) && $arg instanceof ServiceReference ) { return $this->getService($arg); } return $arg; }
  • 62. Towards a real implementation
  • 63. Scope Each time you get a service: Do you want a new object or Does the container must return the same object? $userDef->setGlobal(true); $feedReaderDef->setGlobal(false);
  • 64. Service definition $container = new ServiceContainer(array( PHP 'storage' => new ServiceDefinition('FileSessionStorage'), 'user' => new ServiceDefinition('User', array(new ServiceReference('storage')) ), )); <service id="storage" class="FileSessionStorage" /> XML <service id="user" class="User"> <constructor_arg type="service" id="storage" /> </service>
  • 65. Service configuration Configuration is <parameter key="storage.class"> decoupled from SQLiteSessionStorage the definition </parameter> <parameter key="storage.session_name"> SESSION_ID </parameter> <service id="storage" class="%storage.class%"> <constructor_arg> %storage.session_name% </constructor_arg> </service>
  • 66. Service configuration [storage] class = SQLiteSessionStorage session_name = SESSION_ID <service id="storage" class="%storage.class%"> <constructor_arg> %storage.session_name% </constructor_arg> </service>
  • 67. // Access parameters $sessionName = $container['storage.session_name']; // Access services $user = $container->user;
  • 68. Implementations in PHP •  Crafty: http://phpcrafty.sourceforge.net/ •  Garden: http://garden.tigris.org/ spring •  Stubbles: http://www.stubbles.net/wiki/Docs/IOC Google Guice •  symfony 2 will have its dependency injection framework spring
  • 69. Remember, most of the time, you don’t need a Container to use Dependency Injection
  • 70. You can start to use and benefit from Dependency Injection today
  • 71. by implementing it in your projects by using externals libraries that already use DI without the need of a container
  • 72. symfony Zend Framework ezComponents Doctrine Swift Mailer …
  • 73. Let’s add some i18n fun
  • 74. class User { protected $storage, $i18n; function __construct($storage, $i18n) { $this->storage = $storage; $this->i18n = $i18n; } function setLanguage($language) { $this->storage->set('language', $language); $this->i18n->setLanguage($language); } // ...
  • 75. •  It makes sense that the User class depends on a Storage class –  We can’t use the User object without a Storage •  It does not make sense to tie the User class to the I18n class –  We want to be able to use the User with non internationalized websites
  • 76. class User { protected $storage, $i18n; function __construct($storage, $i18n = null) { $this->storage = $storage; $this->i18n = $i18n; } function setLanguage($language) { $this->storage->set('language', $language); if (!is_null($this->i18n)) { $this->i18n->setLanguage($language); } }
  • 77. class User { protected $storage; function __construct($storage) { $this->storage = $storage; } function setI18n($i18n) { $this->i18n = $i18n; } function setLanguage($language) { $this->storage->set('language', $language); if (!is_null($this->i18n)) { $this->i18n->setLanguage($language); } } }
  • 79. •  The Observer pattern is a way to allow changes in an object to update many other objects •  It is a powerful mechanism to extend applications without having to change the object itself •  Associated words: hook, listener, event, subject, …
  • 80. •  Used by a lot of applications for: –  Plugins (Wordpress, …) –  Bridges between applications •  As a master application: –  Gallery2 (90% events / hooks for user / group create / update / delete, logout, login, ..missing: configuration changes) –  Xaraya (100% events / hooks for user / group create / update / delete, configuration changes, logout, login, ..) –  Wordpress (90% events / hooks for user / update / delete, rewrites, logout, login, ..) –  Drupal (80% maybe?) –  Joomla (100% joomla 1.5; login, logout, create, update, delete user, block, activation, system before and after start)Typo3 (50% maybe?, e.g. no unified create user event / hook) •  As a slave application: –  Gallery 2 –  Phorum.org http://codex.gallery2.org/Gallery2:Embedding:Event-Based_Loose-Coupled_Integration
  • 81. Implementations in PHP •  PEAR_Dispatcher –  http://pear.php.net/package/Event_Dispatcher •  symfony implementation –  http://svn.symfony-project.com/branches/1.1/lib/event/ –  http://www.symfony-project.org/book/1_1/17-Extending-Symfony –  Based on the Cocoa notification center •  Simple and powerful •  Decoupled from symfony •  Simple to use
  • 82. $i18n = new I18n(); $dispatcher = new sfEventDispatcher(); $listener = array($i18n, 'listenToChangeCultureEvent'); $dispatcher->connect('user.change_language', $listener); $storage = new FileSessionStorage(); $user = new User($dispatcher, $storage);
  • 83. class User { protected $storage, $dispatcher; function __construct($dispatcher, $storage) { $this->dispatcher = $dispatcher; $this->storage = $storage; } function setLanguage($language) { $this->storage->set('language', $language); $event = new sfEvent( $this, 'user.change_language', array('language' => $language) ); $this->dispatcher->notify($event); }
  • 84. Notifiers Dispatcher Listeners I18n listens 1 to user.change_culture User notifies Calls I18n callback 2 user.change_culture all listeners is called
  • 85. The User object knows nothing about the I18n one The I18n object knows nothing about the User one They communicate through the Dispatcher object Any class can listen to the ‘user.change_culture’ event and acts accordingly
  • 86. Notifiers Dispatcher Listeners I18n listens 1 to user.change_culture Your class listens to user.change_culture User notifies Calls I18n callback 2 user.change_culture all listeners is called Your class callback is called
  • 87. Notifiers Dispatcher User notifies Calls user.change_culture nothing Very small overhead
  • 88. In this example, the implementation is very simple No interface to implement No need to create an event class for each event An event is just a unique identifier (user.change_culture) some conventions (parameter names)
  • 89. Advantages very simple to use easy to add new arguments to the listener very fast very easy to add a new event, just notify it with a unique name Disadvantages the contract between the listener and the notifier is quite loose
  • 91. function connect($name, $listener) { if (!isset($this->listeners[$name])) { $this->listeners[$name] = array(); } $this->listeners[$name][] = $listener; } function notify(sfEvent $event) { if (!isset($this->listeners[$name])) { foreach ($this->listeners[$event->getName()] as $listener) { call_user_func($listener, $event); } } }
  • 92. •  3 types of notification –  Notify : all listeners are called in turn, no feedback to the notifier •  Logging, … –  Notify Until : all listeners are called until one has « processed » the event. The listener that has processed the event can return something to the caller •  Exceptions, … –  Filter : Each listener filter a given value and can change it. The filtered value is returned to the caller •  HTML content, …
  • 93. Questions? Contact Fabien Potencier fabien.potencier@sensio.com http://www.sensiolabs.com/ http://www.symfony-project.org/