migrating to dependency injection

28
Migrating to Dependency Injection

Upload: josh-adell

Post on 10-May-2015

1.657 views

Category:

Technology


1 download

DESCRIPTION

"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.

TRANSCRIPT

Page 1: Migrating to dependency injection

Migrating toDependency Injection

Page 2: Migrating to dependency injection

@josh_adell

www.servicetrade.com

blog.everymansoftware.com

github.com/jadell/neo4jphp

https://joind.in/10419

Page 3: Migrating to dependency injection

Legacy Code

Began with PHP 5.0 (now 5.3)~80k LoC

Mixed PHP & HTML3 x functions.php with ~9k LoC eachmagic_quotes AND register_globals

No abstract classes or interfacesTightly coupled components

No tests

Page 4: Migrating to dependency injection

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']);

}

}

Page 5: Migrating to dependency injection

Dependency Injection (DI)

Components should notcreate the other components

on which they depend.

Injector injects Dependencies into Consumers

Page 6: Migrating to dependency injection

class PageController {

public function __construct(Database $db) {

$this->repo = new Repository($db);

}

}

========================================================

class PageController {

public function __construct(Repository $repo) {

$this->repo = $repo;

}

}

Page 7: Migrating to dependency injection

DI Container

Wires objects together

I like Pimple: http://pimple.sensiolabs.org/

ParametersServices / Shared Objects

Factory ServicesFactory Callbacks

Page 8: Migrating to dependency injection

$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]);

};

Page 9: Migrating to dependency injection

// Factory Services

$di['user'] = $di->factory(function () {

return new User();

});

// Factory Callbacks

$di['userFactory'] = $di->protect(function ($name) {

return new User($name);

});

Page 10: Migrating to dependency injection

Re-architect the applicationso that object instantiation

only occurs in the DI Container.

Re-architect the applicationso the DI Container

is only referenced from the DI Container.

Page 11: Migrating to dependency injection

Why Bother?

Testing / QualityMaintenance Extensibility

Flexibility

Page 12: Migrating to dependency injection

One Step at a Time1. Create a DI factory method for the class2. Replace "new" with calls to the DI factory method3.

a. Move all shared objects to the constructorb. Move all factory creations to anonymous function in the constructor

4.a. Pass in shared objects from the DI Containerb. Pass in factories callbacks from the DI Container

Repeat from Step 1 for all classes

Page 13: Migrating to dependency injection

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']);

}

}

Page 14: Migrating to dependency injection

Step #1: DI Container method$di['userRepository'] = function () {

return new UserRepository();

};

Page 15: Migrating to dependency injection

Step #2: Replace “new”class UserController {

public function getUser() {

global $di;

$repo = $di['userRepository'];

return $repo->findUser($_GET['id']);

}

}

Page 16: Migrating to dependency injection

Step #3a: Move shared objectsclass 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;

}

}

Page 17: Migrating to dependency injection

Step #3b: Move factory creationclass 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;

}

}

Page 18: Migrating to dependency injection

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

};

}

// ...

Page 19: Migrating to dependency injection

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;

}

// ...

Page 20: Migrating to dependency injection

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;

}

}

Page 21: Migrating to dependency injection

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']);

}

}

Page 22: Migrating to dependency injection

That seems like a lot of steps

Page 23: Migrating to dependency injection

New Requirement

Every time a user submits a report,send an email.

Page 24: Migrating to dependency injection

Setup Event Publisherclass User {

public function __construct(EventEmitter $ee) {

$this->event = $ee;

}

public function submitReport(Report $report) {

// ...

$this->event->publish('newReport', $this, $report);

}

}

Page 25: Migrating to dependency injection

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']);

});

Page 26: Migrating to dependency injection

I have to admit, it's getting better

Page 27: Migrating to dependency injection

Questions?class Presentation {

public function __construct(Presenter $presenter) {

$this->presenter = $presenter;

}

public function askQuestion(Question $question) {

return $this->presenter->answer($question);

}

}

Page 28: Migrating to dependency injection

@josh_adell

www.servicetrade.com

blog.everymansoftware.comgithub.com/jadell/neo4jphp

http://pimple.sensiolabs.org/http://martinfowler.com/articles/injection.html

https://joind.in/10419