IoC with PHP
     Chris Weldon
  Dallas TechFest 2011
Before We Begin


                                            http://bit.ly/rf1pxR


git://github.com/neraath/ioc-php-talk.git
Your Guide: Chris Weldon

•   Fightin’ Texas Aggie

•   .Net and PHP Developer

•   UNIX and Windows Sysadmin

•   Senior Consultant at Improving Enterprises

•   Contact Me: chris@chrisweldon.net
Agile, Microsoft, Open Technologies, UX
Applied Training, Coaching, Mentoring
Certified Consulting
Rural Sourcing
Recruiting Services
Before We Get to IoC...
<?php
class Authenticator {
    private $_repository;

    public function __construct() {
        $this->_repository = new DataAccessLayer();
    }

    public function authenticate($username, $password) {
        $hashedPassword = md5($password);
        $user = $this->_repository->findByUsernameAndPassword(
            $username, $hashedPassword);
        return $user === null;
    }
}
What are the problems?
What are the problems?

•   Strongly coupled to DataAccessLayer
What are the problems?

•   Strongly coupled to DataAccessLayer
                                                    Authenticator
                                                authenticate() : bool




                                                  DataAccessLayer
                                          findByUsernameAndPassword : array
What are the problems?

•   Strongly coupled to DataAccessLayer

•
                                                    Authenticator
    Very inflexible                              authenticate() : bool




                                                  DataAccessLayer
                                          findByUsernameAndPassword : array
What are the problems?

•   Strongly coupled to DataAccessLayer

•
                                                    Authenticator
    Very inflexible                              authenticate() : bool



•   How to configure DataAccessLayer?
                                                  DataAccessLayer
                                          findByUsernameAndPassword : array
What are the problems?

•   Strongly coupled to DataAccessLayer

•
                                                    Authenticator
    Very inflexible                              authenticate() : bool



•   How to configure DataAccessLayer?

    •
                                                  DataAccessLayer
        Let it read configs?               findByUsernameAndPassword : array
What are the problems?

•   Strongly coupled to DataAccessLayer

•
                                                    Authenticator
    Very inflexible                              authenticate() : bool



•   How to configure DataAccessLayer?

    •
                                                  DataAccessLayer
        Let it read configs?               findByUsernameAndPassword : array


•   How to test the Authenticator?
Let’s solve it


•   What are our goals?

    •   Decrease coupling

    •   Increase configurability
<?php
interface IUserRepository {
    function findByUsernameAndPassword($username, $password);
}
class DataAccessLayer implements IUserRepository {
    private $_configParams;
    private $_database;

    public function __construct(array $configParams) {
        $this->_configParams = $configParams;
        $this->_database = Zend_Db::factory('Pdo_Mysql', $this->_configParams);
    }

    public function findByUsernameAndPassword($username, $password) {
        $query = 'SELECT * FROM users WHERE username = ? AND password = ?';
        $result = $this->_database->fetchAll($query, $username, $password);
        return $result;
    }
}
Our Updated Authenticator
<?php
class Authenticator {
    private $_repository;

    public function __construct(IUserRepository $repository) {
        $this->_repository = $repository;
    }

    public function authenticate($username, $password) {
        $hashedPassword = md5($password);
        $user = $this->_repository->findByUsernameAndPassword(
            $username, $hashedPassword);
        return $user === null;
    }
}
Time to Consume
<?php
class LoginController {
    public function login($username, $password) {
        $configuration = Zend_Registry::get('dbconfig');
        $dal = new DataAccessLayer($configuration);
        $authenticator = new Authenticator($dal);
        if ($authenticator->authenticate($username, $password)) {
            // Do something to log the user in.
        }
    }
}
Goal Recap


•   What were our goals?

    •   Decrease coupling

    •   Increase configurability
Goal Recap


•   What were our goals?

    •   Decrease coupling

    •   Increase configurability
Goal Recap


•   What were our goals?

    •   Decrease coupling

    •   Increase configurability
What You Saw Was IoC
•   Inversion of Control changes direction of responsibility

    •   Someone else responsible for creating and providing my
        dependencies

    •   Most commonly applied pattern: Dependency Injection

    •   Follows Dependency Inversion Principle from SOLID

•   Culture War: IoC vs. DI vs. Naming vs. Principles vs. Ideology
Dependency Inversion


•   “High-level modules should not depend upon low level modules. They
    should depend upon abstractions.

•   “Abstractions should not depend upon details. Details should depend
    upon abstractions.”
                                                           Robert Martin
Let’s Draw
Let’s Draw

          Authenticator
      authenticate() : bool




        DataAccessLayer
findByUsernameAndPassword : array
Let’s Draw

          Authenticator
      authenticate() : bool




        DataAccessLayer
findByUsernameAndPassword : array
Let’s Draw
                                                  Authenticator
                                              authenticate() : bool

          Authenticator
      authenticate() : bool

                                                 IUserRepository
                                        findByUsernameAndPassword : array

        DataAccessLayer
findByUsernameAndPassword : array

                                                DataAccessLayer
                                        findByUsernameAndPassword : array
Benefit: Flexibility
<?php
class WebServiceUserRepository implements IUserRepository {
    public function findByUsernameAndPassword($username, $password) {
        // Fetch our user through JSON or SOAP
    }
}

class OAuthRepository implements IUserRepository {
    public function findByUsernameAndPassword($username, $password) {
        // Connect to your favorite OAuth provider
    }
}
Benefit: Testable
<?php
class WhenAuthenticating extends PHPUnit_Framework_TestCase {
    public function testGivenInvalidUsernameAndPasswordShouldReturnFalse() {
        $stub = $this->getMock('IUserRepository');
        $stub->expects($this->any())
             ->method('findByUsernameAndPassword')
             ->will($this->returnValue(null));

        $authenticator = new Authenticator($stub);

        $this->assertFalse($authenticator->authenticate('user', 'pass'));
    }
}
Dependency Injection

•   Now we can inject our dependencies to our consumer classes

•   Still requires some other class to be tightly coupled to both of those

•   Need a container that can help abstract the relationship between the
    interface and implementation
<?php
class UserRepositoryContainer {
    /** @return IUserRepository **/
    public function getRepository() {
        $container = new DataAccessLayer(array(
            'dsn' => 'mysql://localhost/database',
            'username' => 'user',
            'password' => 'pass'
        ));

        return $container;
    }
}

class LoginController {
    public function login($username, $password) {
        $container = new UserRepositoryContainer();
        $repository = $container->getRepository();
        $authenticator = new Authenticator($repository);
        // ...
    }
}
Container Woes

•   No uniform interface by which to access services

•   Still tightly coupled with dependencies

•   Configurability of the container difficult

•   How to auto-inject dependency for configured consumer classes?
Symfony Dependency Injection
                 Container

•   Two Ways to Setup and Use sfServiceContainer

    •   Create subclass

    •   Manual registration via code or config
sfServiceContainer Subclass

<?php
class UserRepositoryContainer extends sfServiceContainer {
    protected function getUserRepositoryService() {
        $container = new DataAccessLayer($this['repository.config']);
        return $container;
    }
}
Consuming the Container
<?php
class LoginController {
    public function login($username, $password) {
        $configuration = Zend_Registry::get('dbconfig');

        $container = new UserRepositoryContainer(array(
            'repository.config' => $configuration
        ));
        $repository = $container->userRepository;

        $authenticator = new Authenticator($repository);
        if ($authenticator->authenticate($username, $password)) {
            // Do something to log the user in.
        }
    }
}
That’s Pretty Nice

•   Configurability a lot easier

•   Uniform interface for accessing services

•   How does this scale when there are lots of dependencies?

•   Aren’t we still coupling the container to the implementation at
    compile time?
The Builder

•   Provides a uniform way of describing services, without custom
    containers

•   For each service description, we have the flexibility to configure an
    object:

    •   At instantiation (Constructor Injection)

    •   Post-instantiation (Setter/Method Injection)
How to Describe a Service

•   Code-based or Config-based

    •   Code-based allows for run-time changing of injection parameters

    •   Config-based provides a way to change parameters between
        environments with no code changes

    •   Not mutually exclusive
Code-Based Description
<?php
// Imagine this is a bootstrap file.
$configuration = Zend_Registry::get('dbconfig');

$builder = new sfServiceContainerBuilder();

$builder->register('user_repository', 'DataAccessLayer')
        ->addArgument($configuration)
        // OR ->addArgument('%repository.config%') from earlier
        ->setShared(false);

$builder->register('authenticator', 'Authenticator')
        ->addArgument(new sfServiceReference('user_repository'));

Zend_Registry::set('di_container', $builder);
Config-Based Description
<?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="user_repository.dsn">mysql://localhost/database</parameter>
    <parameter key="user_repository.username">username</parameter>
    <parameter key="user_repository.password">password</parameter>
  </parameters>
  <services>
    <service id="user_repository" class="DataAccessLayer" shared="false">
      <argument type="collection">
        <argument key="dsn">%user_repository.dsn%</argument>
        <argument key="username">%user_repository.username%</argument>
        <argument key="password">%user_repository.password%</argument>
      </argument>
    </service>
    <service id="authenticator" class="Authenticator">
      <argument type="service" id="user_repository" />
    </service>
  </services>
</container>
Loading the Config

<?php
// Imagine this is a bootstrap file.
$builder = new sfServiceContainerBuilder();

$loader = new sfServiceContainerLoaderFileXml($builder);
$loader->load('/pathTo/services.xml');

Zend_Registry::set('di_container', $builder);
Using the Container

<?php
class LoginController {
    public function login($username, $password) {
        $container = Zend_Registry::get('di_container');
        $authenticator = $container->authenticator;
        if ($authenticator->authenticate($username, $password)) {
            // Do something to log the user in.
        }
    }
}
High Level Picture
        DataAccessLayer
findByUsernameAndPassword : array




         IUserRepository
findByUsernameAndPassword : array
                                     sfServiceContainerBuilder
                                   getService : object
                                   setService : void
                                   hasService : bool
            Authenticator
authenticate() : bool




            IAuthenticator
authenticate() : bool



                                     sfServiceContainerInterface
                                   getService : object
           LoginController         setService : void
        login() : void             hasService : bool
When to Use a DI Container
When to Use a DI Container

•   Not for model objects (e.g. Orders, Documents, etc.)
When to Use a DI Container

•   Not for model objects (e.g. Orders, Documents, etc.)

•   Great for resource requirements (e.g. repositories, loggers, etc.)
When to Use a DI Container

•   Not for model objects (e.g. Orders, Documents, etc.)

•   Great for resource requirements (e.g. repositories, loggers, etc.)

•   Really great for plugin-type architecture
When to Use a DI Container

•   Not for model objects (e.g. Orders, Documents, etc.)

•   Great for resource requirements (e.g. repositories, loggers, etc.)

•   Really great for plugin-type architecture

•   But not necessary to use Dependency Injection!
Other Considerations
Other Considerations

•   Learning curve
Other Considerations

•   Learning curve

•   Tracking dependencies
Other Considerations

•   Learning curve

•   Tracking dependencies

•   Dependency changes
Other Considerations

•   Learning curve

•   Tracking dependencies

•   Dependency changes

•   Setter/method vs. constructor injection
Service Lifetimes


•   setShared() allows you to specify context persistence

    •   If shared, acts like a singleton

    •   Useful if construction is expensive or state persistence required
Let’s Code
Thank You!


                                            http://bit.ly/rf1pxR


git://github.com/neraath/ioc-php-talk.git

IoC with PHP

  • 1.
    IoC with PHP Chris Weldon Dallas TechFest 2011
  • 2.
    Before We Begin http://bit.ly/rf1pxR git://github.com/neraath/ioc-php-talk.git
  • 3.
    Your Guide: ChrisWeldon • Fightin’ Texas Aggie • .Net and PHP Developer • UNIX and Windows Sysadmin • Senior Consultant at Improving Enterprises • Contact Me: chris@chrisweldon.net
  • 4.
    Agile, Microsoft, OpenTechnologies, UX Applied Training, Coaching, Mentoring Certified Consulting Rural Sourcing Recruiting Services
  • 5.
    Before We Getto IoC... <?php class Authenticator { private $_repository; public function __construct() { $this->_repository = new DataAccessLayer(); } public function authenticate($username, $password) { $hashedPassword = md5($password); $user = $this->_repository->findByUsernameAndPassword( $username, $hashedPassword); return $user === null; } }
  • 6.
    What are theproblems?
  • 7.
    What are theproblems? • Strongly coupled to DataAccessLayer
  • 8.
    What are theproblems? • Strongly coupled to DataAccessLayer Authenticator authenticate() : bool DataAccessLayer findByUsernameAndPassword : array
  • 9.
    What are theproblems? • Strongly coupled to DataAccessLayer • Authenticator Very inflexible authenticate() : bool DataAccessLayer findByUsernameAndPassword : array
  • 10.
    What are theproblems? • Strongly coupled to DataAccessLayer • Authenticator Very inflexible authenticate() : bool • How to configure DataAccessLayer? DataAccessLayer findByUsernameAndPassword : array
  • 11.
    What are theproblems? • Strongly coupled to DataAccessLayer • Authenticator Very inflexible authenticate() : bool • How to configure DataAccessLayer? • DataAccessLayer Let it read configs? findByUsernameAndPassword : array
  • 12.
    What are theproblems? • Strongly coupled to DataAccessLayer • Authenticator Very inflexible authenticate() : bool • How to configure DataAccessLayer? • DataAccessLayer Let it read configs? findByUsernameAndPassword : array • How to test the Authenticator?
  • 13.
    Let’s solve it • What are our goals? • Decrease coupling • Increase configurability
  • 14.
    <?php interface IUserRepository { function findByUsernameAndPassword($username, $password); } class DataAccessLayer implements IUserRepository { private $_configParams; private $_database; public function __construct(array $configParams) { $this->_configParams = $configParams; $this->_database = Zend_Db::factory('Pdo_Mysql', $this->_configParams); } public function findByUsernameAndPassword($username, $password) { $query = 'SELECT * FROM users WHERE username = ? AND password = ?'; $result = $this->_database->fetchAll($query, $username, $password); return $result; } }
  • 15.
    Our Updated Authenticator <?php classAuthenticator { private $_repository; public function __construct(IUserRepository $repository) { $this->_repository = $repository; } public function authenticate($username, $password) { $hashedPassword = md5($password); $user = $this->_repository->findByUsernameAndPassword( $username, $hashedPassword); return $user === null; } }
  • 16.
    Time to Consume <?php classLoginController { public function login($username, $password) { $configuration = Zend_Registry::get('dbconfig'); $dal = new DataAccessLayer($configuration); $authenticator = new Authenticator($dal); if ($authenticator->authenticate($username, $password)) { // Do something to log the user in. } } }
  • 17.
    Goal Recap • What were our goals? • Decrease coupling • Increase configurability
  • 18.
    Goal Recap • What were our goals? • Decrease coupling • Increase configurability
  • 19.
    Goal Recap • What were our goals? • Decrease coupling • Increase configurability
  • 20.
    What You SawWas IoC • Inversion of Control changes direction of responsibility • Someone else responsible for creating and providing my dependencies • Most commonly applied pattern: Dependency Injection • Follows Dependency Inversion Principle from SOLID • Culture War: IoC vs. DI vs. Naming vs. Principles vs. Ideology
  • 21.
    Dependency Inversion • “High-level modules should not depend upon low level modules. They should depend upon abstractions. • “Abstractions should not depend upon details. Details should depend upon abstractions.” Robert Martin
  • 22.
  • 23.
    Let’s Draw Authenticator authenticate() : bool DataAccessLayer findByUsernameAndPassword : array
  • 24.
    Let’s Draw Authenticator authenticate() : bool DataAccessLayer findByUsernameAndPassword : array
  • 25.
    Let’s Draw Authenticator authenticate() : bool Authenticator authenticate() : bool IUserRepository findByUsernameAndPassword : array DataAccessLayer findByUsernameAndPassword : array DataAccessLayer findByUsernameAndPassword : array
  • 26.
    Benefit: Flexibility <?php class WebServiceUserRepositoryimplements IUserRepository { public function findByUsernameAndPassword($username, $password) { // Fetch our user through JSON or SOAP } } class OAuthRepository implements IUserRepository { public function findByUsernameAndPassword($username, $password) { // Connect to your favorite OAuth provider } }
  • 27.
    Benefit: Testable <?php class WhenAuthenticatingextends PHPUnit_Framework_TestCase { public function testGivenInvalidUsernameAndPasswordShouldReturnFalse() { $stub = $this->getMock('IUserRepository'); $stub->expects($this->any()) ->method('findByUsernameAndPassword') ->will($this->returnValue(null)); $authenticator = new Authenticator($stub); $this->assertFalse($authenticator->authenticate('user', 'pass')); } }
  • 28.
    Dependency Injection • Now we can inject our dependencies to our consumer classes • Still requires some other class to be tightly coupled to both of those • Need a container that can help abstract the relationship between the interface and implementation
  • 29.
    <?php class UserRepositoryContainer { /** @return IUserRepository **/ public function getRepository() { $container = new DataAccessLayer(array( 'dsn' => 'mysql://localhost/database', 'username' => 'user', 'password' => 'pass' )); return $container; } } class LoginController { public function login($username, $password) { $container = new UserRepositoryContainer(); $repository = $container->getRepository(); $authenticator = new Authenticator($repository); // ... } }
  • 30.
    Container Woes • No uniform interface by which to access services • Still tightly coupled with dependencies • Configurability of the container difficult • How to auto-inject dependency for configured consumer classes?
  • 31.
    Symfony Dependency Injection Container • Two Ways to Setup and Use sfServiceContainer • Create subclass • Manual registration via code or config
  • 32.
    sfServiceContainer Subclass <?php class UserRepositoryContainerextends sfServiceContainer { protected function getUserRepositoryService() { $container = new DataAccessLayer($this['repository.config']); return $container; } }
  • 33.
    Consuming the Container <?php classLoginController { public function login($username, $password) { $configuration = Zend_Registry::get('dbconfig'); $container = new UserRepositoryContainer(array( 'repository.config' => $configuration )); $repository = $container->userRepository; $authenticator = new Authenticator($repository); if ($authenticator->authenticate($username, $password)) { // Do something to log the user in. } } }
  • 34.
    That’s Pretty Nice • Configurability a lot easier • Uniform interface for accessing services • How does this scale when there are lots of dependencies? • Aren’t we still coupling the container to the implementation at compile time?
  • 35.
    The Builder • Provides a uniform way of describing services, without custom containers • For each service description, we have the flexibility to configure an object: • At instantiation (Constructor Injection) • Post-instantiation (Setter/Method Injection)
  • 36.
    How to Describea Service • Code-based or Config-based • Code-based allows for run-time changing of injection parameters • Config-based provides a way to change parameters between environments with no code changes • Not mutually exclusive
  • 37.
    Code-Based Description <?php // Imaginethis is a bootstrap file. $configuration = Zend_Registry::get('dbconfig'); $builder = new sfServiceContainerBuilder(); $builder->register('user_repository', 'DataAccessLayer') ->addArgument($configuration) // OR ->addArgument('%repository.config%') from earlier ->setShared(false); $builder->register('authenticator', 'Authenticator') ->addArgument(new sfServiceReference('user_repository')); Zend_Registry::set('di_container', $builder);
  • 38.
    Config-Based Description <?xml version="1.0"?> <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="user_repository.dsn">mysql://localhost/database</parameter> <parameter key="user_repository.username">username</parameter> <parameter key="user_repository.password">password</parameter> </parameters> <services> <service id="user_repository" class="DataAccessLayer" shared="false"> <argument type="collection"> <argument key="dsn">%user_repository.dsn%</argument> <argument key="username">%user_repository.username%</argument> <argument key="password">%user_repository.password%</argument> </argument> </service> <service id="authenticator" class="Authenticator"> <argument type="service" id="user_repository" /> </service> </services> </container>
  • 39.
    Loading the Config <?php //Imagine this is a bootstrap file. $builder = new sfServiceContainerBuilder(); $loader = new sfServiceContainerLoaderFileXml($builder); $loader->load('/pathTo/services.xml'); Zend_Registry::set('di_container', $builder);
  • 40.
    Using the Container <?php classLoginController { public function login($username, $password) { $container = Zend_Registry::get('di_container'); $authenticator = $container->authenticator; if ($authenticator->authenticate($username, $password)) { // Do something to log the user in. } } }
  • 41.
    High Level Picture DataAccessLayer findByUsernameAndPassword : array IUserRepository findByUsernameAndPassword : array sfServiceContainerBuilder getService : object setService : void hasService : bool Authenticator authenticate() : bool IAuthenticator authenticate() : bool sfServiceContainerInterface getService : object LoginController setService : void login() : void hasService : bool
  • 42.
    When to Usea DI Container
  • 43.
    When to Usea DI Container • Not for model objects (e.g. Orders, Documents, etc.)
  • 44.
    When to Usea DI Container • Not for model objects (e.g. Orders, Documents, etc.) • Great for resource requirements (e.g. repositories, loggers, etc.)
  • 45.
    When to Usea DI Container • Not for model objects (e.g. Orders, Documents, etc.) • Great for resource requirements (e.g. repositories, loggers, etc.) • Really great for plugin-type architecture
  • 46.
    When to Usea DI Container • Not for model objects (e.g. Orders, Documents, etc.) • Great for resource requirements (e.g. repositories, loggers, etc.) • Really great for plugin-type architecture • But not necessary to use Dependency Injection!
  • 47.
  • 48.
  • 49.
    Other Considerations • Learning curve • Tracking dependencies
  • 50.
    Other Considerations • Learning curve • Tracking dependencies • Dependency changes
  • 51.
    Other Considerations • Learning curve • Tracking dependencies • Dependency changes • Setter/method vs. constructor injection
  • 52.
    Service Lifetimes • setShared() allows you to specify context persistence • If shared, acts like a singleton • Useful if construction is expensive or state persistence required
  • 53.
  • 54.
    Thank You! http://bit.ly/rf1pxR git://github.com/neraath/ioc-php-talk.git