"Dependency injection" (DI) seems like one of those hot buzzwords that will solve all your problems. But what is DI really? How does it help keep code clean and maintainable? And how do you take a legacy codebase and rewrite it to take advantage of DI? This talk takes an application written without DI and walks through the steps for "injecting" DI into the code. Learn the difference between "dependency injection" and "dependency injection containers". See how DI makes things like event-driven architectures simple to implement. And learn how DI leads to code that is easier to debug and test.
3. Legacy Code
Began with PHP 5.0 (now 5.3)
~80k LoC
Mixed PHP & HTML
3 x functions.php with ~9k LoC each
magic_quotes AND register_globals
No abstract classes or interfaces
Tightly coupled components
No tests
4. class UserRepository {
public function findUser($id) {
$db = new Database(/* connection params*/);
$userInfo = $db->query(/* User query with $id */);
$user = new User();
$user->setProperties($userInfo);
return $user;
}
}
========================================================
class UserController {
public function getUser() {
$repo = new UserRepository();
return $repo->findUser($_GET['id']);
}
}
6. class PageController {
public function __construct(
Database $db) {
$this->repo = new Repository($db);
}
}
========================================================
class PageController {
public function __construct(
Repository $repo) {
$this->repo = $repo;
}
}
7. DI Container
Wires objects together
I like Pimple: http://pimple.sensiolabs.org/
Parameters
Services / Shared Objects
Factory Services
Factory Callbacks
8. $di = new Pimple();
// Parameters
$di['dbHost'] = "localhost";
$di['dbUser'] = "username";
$di['dbPass'] = "password";
// Services / Shared Objects
$di['database'] = function ($di) {
return new Database($di['dbHost'], $di['dbUser'], $di
['dbPass']);
};
$di['userRepository'] = function ($di) {
return new UserRepository($di['database]);
};
10. Re-architect the application
so that object instantiation
only occurs in the DI Container.
Re-architect the application
so the DI Container
is only referenced from the DI Container.
12. One Step at a Time
1.
2.
3.
Create a DI factory method for the class
Replace "new" with calls to the DI factory method
a.
b.
Move all shared objects to the constructor
Move all factory creations to anonymous function in the constructor
a.
b.
Pass in shared objects from the DI Container
Pass in factories callbacks from the DI Container
4.
Repeat from Step 1 for all classes
13. class UserRepository {
public function findUser($id) {
$db = new Database(/* connection params*/);
$userInfo = $db->query(/* User query with $id */);
$user = new User();
$user->setProperties($userInfo);
return $user;
}
}
========================================================
class UserController {
public function getUser() {
$repo = new UserRepository();
return $repo->findUser($_GET['id']);
}
}
14. Step #1: DI Container method
$di['userRepository'] = function () {
return new UserRepository();
};
15. Step #2: Replace “new”
class UserController {
public function getUser() {
global $di;
$repo = $di['userRepository'];
return $repo->findUser($_GET['id']);
}
}
16. Step #3a: Move shared objects
class UserRepository {
public function __construct() {
$this->db = new Database(/* connection params*/);
}
public function findUser($id) {
$userInfo = $this->db->query(/* User query with $id */);
$user = new User();
$user->setProperties($userInfo);
return $user;
}
}
17. Step #3b: Move factory creation
class UserRepository {
public function __construct() {
$this->db = new Database(/* connection params*/);
$this->userFactory = function () {
return new User();
};
}
public function findUser($id) {
$userInfo = $this->db->query(/* User query with $id */);
$user = call_user_func($this->userFactory);
$user->setProperties($userInfo);
return $user;
}
}
18. Step #4a: Pass in shared objects
$di['database'] = function () {
return new Database();
};
$di['userRepository'] = function (
$di) {
return new UserRepository(
$di['database']);
};
========================================================
class UserRepository {
public function __construct(
Database $db) {
$this->db = $db;
$this->userFactory = function () {
return new User();
};
}
// ...
19. Step #4b: Pass in factory callbacks
$di['userFactory'] = $di->protect(function () {
return new User();
});
$di['userRepository'] = function ($di) {
return new UserRepository($di['database'], $di['userFactory']);
};
========================================================
class UserRepository {
public function __construct(Database $db, callable $userFactory) {
$this->db = $db;
$this->userFactory = $userFactory;
}
// ...
20. Done with UserRepository!
class UserRepository {
public function __construct(Database $db, callable $userFactory) {
$this->db = $db;
$this->userFactory = $userFactory;
}
public function findUser($id) {
$userInfo = $this->db->query(/* User query with $id */);
$user = call_user_func($this->userFactory);
$user->setProperties($userInfo);
return $user;
}
}
21. Repeat for UserController
$di['userController'] = function ($di) {
return new UserController($di['userRepository']);
};
========================================================
class UserController {
public function __construct(UserRepository $repo) {
$this->userRepo = $repo;
}
public function getUser() {
global $di;
$repo = $di['userRepository'];
return $this->userRepo->findUser($_GET['id']);
}
}
24. Setup Event Publisher
class User {
public function __construct(
EventEmitter $ee) {
$this->event = $ee;
}
public function submitReport(Report $report) {
// ...
$this->event->publish('newReport', $this, $report);
}
}
25. Setup Event Subscribers
$di['eventEmitter'] = function () {
return new EventEmitter();
};
$di['emailService'] = function ($di) {
return new EmailService();
};
$di['userFactory'] = $di->protect(function () use ($di)) {
$di['eventEmitter']->subscribe('newReport', $di['emailService']);
return new User($di['eventEmitter']);
});
27. Questions?
class Presentation {
public function __construct(Presenter $presenter) {
$this->presenter = $presenter;
}
public function askQuestion(Question $question) {
return $this->presenter->answer($question);
}
}