2. Before We Begin
http://bit.ly/rf1pxR
git://github.com/neraath/ioc-php-talk.git
3. 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
5. 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;
}
}
7. What are the problems?
• Strongly coupled to DataAccessLayer
8. What are the problems?
• Strongly coupled to DataAccessLayer
Authenticator
authenticate() : bool
DataAccessLayer
findByUsernameAndPassword : array
9. What are the problems?
• Strongly coupled to DataAccessLayer
•
Authenticator
Very inflexible authenticate() : bool
DataAccessLayer
findByUsernameAndPassword : array
10. What are the problems?
• Strongly coupled to DataAccessLayer
•
Authenticator
Very inflexible authenticate() : bool
• How to configure DataAccessLayer?
DataAccessLayer
findByUsernameAndPassword : array
11. What are the problems?
• Strongly coupled to DataAccessLayer
•
Authenticator
Very inflexible authenticate() : bool
• How to configure DataAccessLayer?
•
DataAccessLayer
Let it read configs? findByUsernameAndPassword : array
12. 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?
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
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;
}
}
16. 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.
}
}
}
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 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
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
26. 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
}
}
27. 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'));
}
}
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
33. 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.
}
}
}
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 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
37. 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);
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
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.
}
}
}
43. When to Use a DI Container
• Not for model objects (e.g. Orders, Documents, etc.)
44. When to Use a DI Container
• Not for model objects (e.g. Orders, Documents, etc.)
• Great for resource requirements (e.g. repositories, loggers, etc.)
45. 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
46. 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!
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