migrating to dependency injection

Post on 10-May-2015

1.659 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

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

Migrating toDependency Injection

@josh_adell

www.servicetrade.com

blog.everymansoftware.com

github.com/jadell/neo4jphp

https://joind.in/10419

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

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

}

}

Dependency Injection (DI)

Components should notcreate the other components

on which they depend.

Injector injects Dependencies into Consumers

class PageController {

public function __construct(Database $db) {

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

}

}

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

class PageController {

public function __construct(Repository $repo) {

$this->repo = $repo;

}

}

DI Container

Wires objects together

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

ParametersServices / Shared Objects

Factory ServicesFactory Callbacks

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

};

// Factory Services

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

return new User();

});

// Factory Callbacks

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

return new User($name);

});

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.

Why Bother?

Testing / QualityMaintenance Extensibility

Flexibility

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

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

}

}

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

return new UserRepository();

};

Step #2: Replace “new”class UserController {

public function getUser() {

global $di;

$repo = $di['userRepository'];

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

}

}

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;

}

}

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;

}

}

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

};

}

// ...

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;

}

// ...

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;

}

}

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

}

}

That seems like a lot of steps

New Requirement

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

Setup Event Publisherclass User {

public function __construct(EventEmitter $ee) {

$this->event = $ee;

}

public function submitReport(Report $report) {

// ...

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

}

}

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

});

I have to admit, it's getting better

Questions?class Presentation {

public function __construct(Presenter $presenter) {

$this->presenter = $presenter;

}

public function askQuestion(Question $question) {

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

}

}

@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

top related