SlideShare una empresa de Scribd logo
1 de 54
Descargar para leer sin conexión
Dependency Injection
 Dependency Inversion Principle
Highly Coupled Code is Bad
Some coupling is unavoidable
Decoupled Code is Good
Easier to test
Easier to debug
Easier to reason about
Common Dependencies
Infrastructure - eg Database, third party module
Utility objects - eg Logger
Environment - eg FileSystem, system clock
Static Method calls - eg DataAccess::saveUser($u)
“Current” Dependencies
Request/Response
Environment variables
User details
Session details
Time
Spot the dependencies
class OrderProcessor {

function __construct() {

$this->orderRepository = new MysqlOrderRepository();

}
function completeOrder($order) {

  global $logger;

$order->completedAt = new DateTimeImmutable;

$logger->log("Order {$order->id} is complete");

$this->orderRepository->save($order);

Mailer::sendOrderCompleteEmail($order);

}

}
Spot the dependencies
class OrderProcessor {

function __construct() {

$this->orderRepository = new MysqlOrderRepository();

}
function completeOrder($order) {

  global $logger;

$order->completedAt = new DateTimeImmutable;

$logger->log("Order {$order->id} is complete");

$this->orderRepository->save($order);

Mailer::sendOrderCompleteEmail($order);

}

}
Hard Questions
Q: What does OrderProcessor depend on?

A: Read the entire class to find out!
Q: Where is Mailer used in my application?

A: Grep everything in your project for “Mailer::".
Q: How can I test completeOrder() without sending emails?

A: ¯_(ツ)_/¯ *
* Yes, there are workarounds, but this isn't a testing talk.
What is Dependency Injection?
Dependency injection is the practice of pushing (injecting) dependencies
into an object, rather than having objects find their dependencies on their
own.
This isn't the same as the Dependency Inversion Principle in SOLID.
We'll get to that later.
DI Techniques
Constructor Injection
Setter or Property Injection
Parameter Injection
Constructor Injection
class ClassThatNeedsDatabase

{

private $db;
public function __construct(Database $db) {

$this->db = $db;

}

}
Pros
Makes dependencies explicit
Can’t modify dependencies after instantiation
Discourages violation of Single Responsibility Principle

CONS
May have a lot of constructor parameters
May never use most dependencies
Could be the wrong point in the object life cycle
Many Constructor Parameters
public function __construct(

Twig $view,

AuthorizationServer $auth,

LogRepository $errorRepo,

AuthRequestRepository $authRequestRepo,

  ClientRepository $clientRepo,

UserRepository $userRepo,

ResourceServer $resAuth,

AccessTokenRepository $accessTokenRepo,

UserValidatorInterface $validator) 

{/* a whole bunch of property assigns */}
Antipattern #1: __construct($options)
"Just throw everything in an $options array!"
Setter Injection
class ClassThatUsesLoggingButDoesNotRequireIt

{

private $logger = null;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}

}
Property Injection
class ClassThatMightWantAThingCalledALogger

{

public $logger = null;

}
class SlightlySaferClassThatWantsALogger

{

public Logger $logger = null;

} // PHP 7.4+
Pros
Lazier loading
Works well with optional dependencies
Flexible across class lifecycle
Don’t need every dependency for every method call
Can change dependencies without re-instantiating the class
Cons
Harder to quickly see which dependencies a class has
Existence of dependencies in fully instantiated class not guaranteed
Null checks inside the code
Conditional injection outside the code
Checking Property
public function fileTPSReport(Report $rpt) {

/* do some stuff */

if ($this->logger) {

$this->logger->log('Did a thing');

}
/* do more stuff */

if ($this->logger) {

$this->logger->log('Did things');

}

}
Null Property
public function fileTPSReport(Report $rpt) {

/* do some stuff */

$this->logger->log('Did a thing’);
/* do more stuff */

$this->logger->log('Did things');

}
class NullLogger implements Logger {

public function log($message) {

/** noop **/

}

}
DON'T IMPLEMENT THIS NULL
LOGGER...
...BECAUSE THERE'S ONE BUILT INTO PSR/LOG
TRAITS + SETTER INJECTION
Add setter to trait
Import trait into classes
Implement interface on classes
Configure DI container to setter-inject based on interface
e.g. PsrLog{LoggerAwareInterface, LoggerAwareTrait}
Sorry, you can't have an interface implement a trait directly.
Parameter Injection
public function __invoke(

SlimHttpRequest $request,

SlimHttpResponse $response,

array $args = [])

{
/** do something, return a Response **/
}
Parameter Injection
public function orderAction(

IlluminateHttpRequest $request,

AppServicesOrderService $orderService)

{
/** do something, return a Response **/
}
Pros
Does not clutter your object with refs to collaborators which are not needed

CONS
Almost everything else about it
Moves the problem of dependency management to the caller
Mix & Match as Needed
Dependency Inversion Principle
High level modules should not depend on low level modules; both should
depend on abstractions.
Abstractions should not depend on details.  Details should depend upon
abstractions.
Tl;dr: use, and expose, interfaces with just enough functionality to get the job
done.
Abstrations Should Not Be Leaky
class Camry implements HasGasPedal {
public function pressAccelerator();
} // namespace ToyotaVehicles
class Model3 implements HasGasPedal {
public function pressAccelerator();
} // namespace TeslaVehicles
Abstrations Should Not Be Leaky
class MysqlUserRepo implements UserRepository {

public function getById(int $id): ?User {}

}
class ElasticUserRepo implements UserRepository {

public function getById(int $id): ?User {}

}
class gRPCUserAPI implements UserRepository {

public function getById(int $id): ?User {}

}
interface User { /** various signatures **/ }
Let’s Do Some Refactoring
class OrderProcessor {

function __construct() {

$this->orderRepository = new MysqlOrderRepository();

}
function completeOrder($order) {

  global $logger;

$order->completedAt = new DateTimeImmutable;

$logger->log("Order {$order->id} is complete");

$this->orderRepository->save($order);

Mailer::sendOrderCompleteEmail($order);

}

}
Let’s Do Some Refactoring
class OrderProcessor {

function __construct(OrderRepository $orderRepo, Logger $logger) {

$this->orderRepository = $orderRepo;

$this->logger = $logger;

}
function completeOrder($order) {

$order->completedAt = new DateTimeImmutable;

$this->logger->log(“Order {$order->id} is complete");

$this->orderRepository->save($order);

Mailer::sendOrderCompleteEmail($order);

}

}
Let’s Do Some Refactoring
class OrderProcessor {

function __construct(OrderRepository $orderRepo, Logger $logger,

DateTimeImmutable $now, Mailer $mailer) {

$this->orderRepository = $orderRepo;

$this->logger = $logger;

$this->now = $now;

$this->mailer = $mailer;

}
function completeOrder($order) {

$order->completedAt = $this->now;

$this->logger->log(“Order {$order->id} is complete");

$this->orderRepository->save($order);

$this->mailer->sendOrderCompleteEmail($order);

}

}
Let’s Do Some Refactoring
class OrderProcessor {

function __construct(OrderRepository $orderRepo, Logger $logger,

Mailer $mailer) {

$this->orderRepository = $orderRepo;

$this->logger = $logger;

$this->now = $now;

}
function completeOrder($order, DateTimeImmutable $now) {

$order->completedAt = $now;

$this->logger->log(“Order {$order->id} is complete");

$this->orderRepository->save($order);

$this->mailer->sendOrderCompleteEmail($order);

}

}
What is a DI Container?
A dependency injection container is an object used to manage the
instantiation of other objects.
If you have one, it will be the place where the "new" keyword gets used
more than anywhere else in your app, either via configuration code you
write or under the hood.
What a DI Container is NOT
Not the only place you can (or should) use the "new" keyword in your
application.
Factories
Value objects
Not required for dependency injection.
Not to be used as a Service Locator
PSR-11(FKA Container-Interop)
namespace PsrContainer;
interface ContainerInterface {

public function get($id);

public function has($id);

}
Twittee: A DI Container in a 140 Tweet
class Container {

protected $s=array();

function __set($k, $c) { $this->s[$k]=$c; }

function __get($k) { return $this->s[$k]($this); }

}
Using Twitter
class NeedsALogger {

  private $logger;

public function __construct(Logger $logger) {

  $this->logger = $logger;

  }

}

class Logger {}

$c = new Container;

$c->logger = function() { return new Logger; };

$c->myService = function($c) {

return new NeedsALogger($c->logger);

};

var_dump($c->myService); // includes the Logger
Twittee++: A PSR-11 Container in a
280 Tweet
class Container implements PsrContainerContainerInterface {

protected $s=array();

function __set($k, $c) { $this->s[$k]=$c; }

function __get($k) { return $this->s[$k]($this); }

function get($k) { return $this->s[$k]($this); }

function has($k) { return isset($this->s[$k]); }

}
Full DI Containers
Every major framework has one
Symfony (DependencyInjection component)
Zend (ServiceManager)
Laravel (IlluminateContainer)
Standalone ones for use elsewhere
Pimple (used in Slim)
LeagueContainer
Aura.Di
Disco
Beware Service Location
Antipattern #2” Service Location
class OrderProcessor {

function __construct(Container Interface $c) {

$this->orderRepository = $c->get(‘OrderRepository’);

$this->logger = $c->get(‘Logger’);

$this->mailer = $c->get(‘Mailer’);

}
function completeOrder($order, DateTimeImmutable $now) {

$order->completedAt = $now;

$this->logger->log(“Order {$order->id} is complete");

$this->orderRepository->save($order);

$this->mailer->sendOrderCompleteEmail($order);

}

}
Antipattern #2” Service Location
class OrderProcessor {

protected $c;

function __construct(Container Interface $c) {

$this->orderRepository = $c->get(‘OrderRepository’);

$this->c = $c;

}
function completeOrder($order, DateTimeImmutable $now) {

$order->completedAt = $now;

$this->c->get(‘Logger’)—>log(“Order {$order->id} is complete");

$this->orderRepository->save($order);

$this->c->get(‘Mailer’)—>sendOrderCompleteEmail($order);

}

}
Using a Container
In a typical application you will use the container from
within your “controllers” and use them to inject
dependencies into your “models”.
Pimple - PSR-11 Compliant
$c = new PimpleContainer;
$c[NeedsALogger::class] = function($c) {

return new NeedsALogger($c['logger']);

};
$c['logger'] = function() {

return new Logger;

};
var_dump($c[NeedsALogger::class]); // NeedsALogger
Pimple
Use $c->factory(callable) if you don't want the same instance every time you
ask for a dependency.
Use $c->protect(callable) if you want to add a closure to your container.
Use $c->raw(dep) if you want to get a closure you set without using protect()
Not much magic
Default container of Slim 3
Refactor a Slim Route
$app = new SlimApp();
$app->get('/', function(Request $req, Response $res) {

$userRepository = new UserRepository(new PDO(/* */));

$users = $userRepository->listAll();

return $res->withJson($users);

});
Refactor a Slim Route
$app = new SlimApp();

$c = $app->getContainer();

$c['db'] = function($c) { return new PDO(/* */); };
$app->get('/', function(Request $req, Response $res) use ($c) {

$userRepository = new UserRepository($c[‘db’]);

$users = $userRepository->listAll();

return $res->withJson($users);

});
Refactor a Slim Route
$app = new SlimApp();

$c = $app->getContainer();

$c['db'] = function($c) { return new PDO(/* */); };

$c['userRepository'] = function($c) {

return new UserRepository($c['db']);

};
$app->get('/', function(Request $req, Response $res) {

$userRepository = $this->get(‘userRepository');

$users = $userRepository->listAll();

return $res->withJson($users);

});
Refactor a Slim Route
class GetUsersAction implements SlimControllerInterface {

protected $userRepository;
public function __construct(UserRepository $repo) {

$this->userRepository = $repo;

}
public function __invoke(Request $req, Response $res) {

$users = $this->userRepository->listAll();

return $res->withJson($users);

}

};

};
Refactor a Slim Route
$app = new SlimApp();

$c = $app->getContainer();

$c['db'] = function($c) { return new PDO(/* */); };

$c['userRepository'] = function($c) {

return new UserRepository($c['db']);

};

$c['getUsers'] = function($c) {

return new GetUsersAction(UserRepository $c['userRepository']);

};
$app->get('/', 'getUsers');
Recap
Dependencies in code are unavoidable, but that
doesn’t mean they need to be unmanageable
Inverting dependencies is a way to create more
flexible software
DI containers are a helpful tool for maintaining single
responsibility within objects
Resources
@iansltx
@jcarouth
Alena Holligan
• Wife, and Mother of 3 young children
• PHP Teacher at Treehouse
• Portland PHP User Group Leader
• Cascadia PHP Conference (cascadiaphp.com)
@alenaholligan alena@holligan.us https://joind.in/talk/b024a

Más contenido relacionado

La actualidad más candente

Design Patterns in PHP5
Design Patterns in PHP5 Design Patterns in PHP5
Design Patterns in PHP5
Wildan Maulana
 
Spring 3: What's New
Spring 3: What's NewSpring 3: What's New
Spring 3: What's New
Ted Pennings
 
Easy data-with-spring-data-jpa
Easy data-with-spring-data-jpaEasy data-with-spring-data-jpa
Easy data-with-spring-data-jpa
Staples
 

La actualidad más candente (20)

SOLID PRINCIPLES
SOLID PRINCIPLESSOLID PRINCIPLES
SOLID PRINCIPLES
 
Java Persistence API
Java Persistence APIJava Persistence API
Java Persistence API
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Sqlapi0.1
Sqlapi0.1Sqlapi0.1
Sqlapi0.1
 
Design Patterns in PHP5
Design Patterns in PHP5 Design Patterns in PHP5
Design Patterns in PHP5
 
Oleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoCOleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoC
 
JDBC - JPA - Spring Data
JDBC - JPA - Spring DataJDBC - JPA - Spring Data
JDBC - JPA - Spring Data
 
Advanced Java - Praticals
Advanced Java - PraticalsAdvanced Java - Praticals
Advanced Java - Praticals
 
Advance Java Programs skeleton
Advance Java Programs skeletonAdvance Java Programs skeleton
Advance Java Programs skeleton
 
Weaponizing the Windows API with Metasploit's Railgun
Weaponizing the Windows API with Metasploit's RailgunWeaponizing the Windows API with Metasploit's Railgun
Weaponizing the Windows API with Metasploit's Railgun
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing Strategies
 
Solid principles
Solid principlesSolid principles
Solid principles
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Spring 3: What's New
Spring 3: What's NewSpring 3: What's New
Spring 3: What's New
 
Easy data-with-spring-data-jpa
Easy data-with-spring-data-jpaEasy data-with-spring-data-jpa
Easy data-with-spring-data-jpa
 
Hibernate
Hibernate Hibernate
Hibernate
 
Solid Software Design Principles
Solid Software Design PrinciplesSolid Software Design Principles
Solid Software Design Principles
 
JPA Best Practices
JPA Best PracticesJPA Best Practices
JPA Best Practices
 
PHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodePHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better Code
 
jQuery secrets
jQuery secretsjQuery secrets
jQuery secrets
 

Similar a Dependency Injection

ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf Conference
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
Alexei Gorobets
 

Similar a Dependency Injection (20)

Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
 
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
 
Effective PHP. Part 4
Effective PHP. Part 4Effective PHP. Part 4
Effective PHP. Part 4
 
Easy rest service using PHP reflection api
Easy rest service using PHP reflection apiEasy rest service using PHP reflection api
Easy rest service using PHP reflection api
 
Zend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching loggingZend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching logging
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
Laravel for Web Artisans
Laravel for Web ArtisansLaravel for Web Artisans
Laravel for Web Artisans
 
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
 
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2
 
Dependency Injection in Laravel
Dependency Injection in LaravelDependency Injection in Laravel
Dependency Injection in Laravel
 
What's New In Laravel 5
What's New In Laravel 5What's New In Laravel 5
What's New In Laravel 5
 
Modularity and Layered Data Model
Modularity and Layered Data ModelModularity and Layered Data Model
Modularity and Layered Data Model
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
 
Struts2 notes
Struts2 notesStruts2 notes
Struts2 notes
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 

Más de Alena Holligan

Más de Alena Holligan (20)

2023 Longhorn PHP - Learn to Succeed .pdf
2023 Longhorn PHP - Learn to Succeed .pdf2023 Longhorn PHP - Learn to Succeed .pdf
2023 Longhorn PHP - Learn to Succeed .pdf
 
Environmental variables
Environmental variablesEnvironmental variables
Environmental variables
 
Dev parent
Dev parentDev parent
Dev parent
 
Dependency Management
Dependency ManagementDependency Management
Dependency Management
 
Experiential Project Design
Experiential Project DesignExperiential Project Design
Experiential Project Design
 
Reduce Reuse Refactor
Reduce Reuse RefactorReduce Reuse Refactor
Reduce Reuse Refactor
 
Organization Patterns: MVC
Organization Patterns: MVCOrganization Patterns: MVC
Organization Patterns: MVC
 
When & Why: Interfaces, abstract classes, traits
When & Why: Interfaces, abstract classes, traitsWhen & Why: Interfaces, abstract classes, traits
When & Why: Interfaces, abstract classes, traits
 
Object Features
Object FeaturesObject Features
Object Features
 
Obect-Oriented Collaboration
Obect-Oriented CollaborationObect-Oriented Collaboration
Obect-Oriented Collaboration
 
WordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPressWordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPress
 
Let's Talk Scope
Let's Talk ScopeLet's Talk Scope
Let's Talk Scope
 
Demystifying Object-Oriented Programming #phpbnl18
Demystifying Object-Oriented Programming #phpbnl18Demystifying Object-Oriented Programming #phpbnl18
Demystifying Object-Oriented Programming #phpbnl18
 
Exploiting the Brain for Fun and Profit
Exploiting the Brain for Fun and ProfitExploiting the Brain for Fun and Profit
Exploiting the Brain for Fun and Profit
 
Environmental Variables
Environmental VariablesEnvironmental Variables
Environmental Variables
 
Learn to succeed
Learn to succeedLearn to succeed
Learn to succeed
 
Demystifying oop
Demystifying oopDemystifying oop
Demystifying oop
 
Demystifying Object-Oriented Programming - PHP[tek] 2017
Demystifying Object-Oriented Programming - PHP[tek] 2017Demystifying Object-Oriented Programming - PHP[tek] 2017
Demystifying Object-Oriented Programming - PHP[tek] 2017
 
Demystifying Object-Oriented Programming - PHP UK Conference 2017
Demystifying Object-Oriented Programming - PHP UK Conference 2017Demystifying Object-Oriented Programming - PHP UK Conference 2017
Demystifying Object-Oriented Programming - PHP UK Conference 2017
 
Exploiting the Brain for Fun & Profit #zendcon2016
Exploiting the Brain for Fun & Profit #zendcon2016Exploiting the Brain for Fun & Profit #zendcon2016
Exploiting the Brain for Fun & Profit #zendcon2016
 

Último

TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 

Último (20)

Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
Pharm-D Biostatistics and Research methodology
Pharm-D Biostatistics and Research methodologyPharm-D Biostatistics and Research methodology
Pharm-D Biostatistics and Research methodology
 
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdfAzure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
 
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 

Dependency Injection

  • 2. Highly Coupled Code is Bad Some coupling is unavoidable
  • 3. Decoupled Code is Good Easier to test Easier to debug Easier to reason about
  • 4. Common Dependencies Infrastructure - eg Database, third party module Utility objects - eg Logger Environment - eg FileSystem, system clock Static Method calls - eg DataAccess::saveUser($u)
  • 6. Spot the dependencies class OrderProcessor {
 function __construct() {
 $this->orderRepository = new MysqlOrderRepository();
 } function completeOrder($order) {
   global $logger;
 $order->completedAt = new DateTimeImmutable;
 $logger->log("Order {$order->id} is complete");
 $this->orderRepository->save($order);
 Mailer::sendOrderCompleteEmail($order);
 }
 }
  • 7. Spot the dependencies class OrderProcessor {
 function __construct() {
 $this->orderRepository = new MysqlOrderRepository();
 } function completeOrder($order) {
   global $logger;
 $order->completedAt = new DateTimeImmutable;
 $logger->log("Order {$order->id} is complete");
 $this->orderRepository->save($order);
 Mailer::sendOrderCompleteEmail($order);
 }
 }
  • 8. Hard Questions Q: What does OrderProcessor depend on?
 A: Read the entire class to find out! Q: Where is Mailer used in my application?
 A: Grep everything in your project for “Mailer::". Q: How can I test completeOrder() without sending emails?
 A: ¯_(ツ)_/¯ * * Yes, there are workarounds, but this isn't a testing talk.
  • 9. What is Dependency Injection? Dependency injection is the practice of pushing (injecting) dependencies into an object, rather than having objects find their dependencies on their own. This isn't the same as the Dependency Inversion Principle in SOLID. We'll get to that later.
  • 10. DI Techniques Constructor Injection Setter or Property Injection Parameter Injection
  • 11. Constructor Injection class ClassThatNeedsDatabase
 {
 private $db; public function __construct(Database $db) {
 $this->db = $db;
 }
 }
  • 12. Pros Makes dependencies explicit Can’t modify dependencies after instantiation Discourages violation of Single Responsibility Principle
 CONS May have a lot of constructor parameters May never use most dependencies Could be the wrong point in the object life cycle
  • 13. Many Constructor Parameters public function __construct(
 Twig $view,
 AuthorizationServer $auth,
 LogRepository $errorRepo,
 AuthRequestRepository $authRequestRepo,
   ClientRepository $clientRepo,
 UserRepository $userRepo,
 ResourceServer $resAuth,
 AccessTokenRepository $accessTokenRepo,
 UserValidatorInterface $validator) 
 {/* a whole bunch of property assigns */}
  • 14. Antipattern #1: __construct($options) "Just throw everything in an $options array!"
  • 15. Setter Injection class ClassThatUsesLoggingButDoesNotRequireIt
 {
 private $logger = null; public function setLogger(Logger $logger) { $this->logger = $logger; }
 }
  • 16. Property Injection class ClassThatMightWantAThingCalledALogger
 {
 public $logger = null;
 } class SlightlySaferClassThatWantsALogger
 {
 public Logger $logger = null;
 } // PHP 7.4+
  • 17. Pros Lazier loading Works well with optional dependencies Flexible across class lifecycle Don’t need every dependency for every method call Can change dependencies without re-instantiating the class
  • 18. Cons Harder to quickly see which dependencies a class has Existence of dependencies in fully instantiated class not guaranteed Null checks inside the code Conditional injection outside the code
  • 19. Checking Property public function fileTPSReport(Report $rpt) {
 /* do some stuff */
 if ($this->logger) {
 $this->logger->log('Did a thing');
 } /* do more stuff */
 if ($this->logger) {
 $this->logger->log('Did things');
 }
 }
  • 20. Null Property public function fileTPSReport(Report $rpt) {
 /* do some stuff */
 $this->logger->log('Did a thing’); /* do more stuff */
 $this->logger->log('Did things');
 } class NullLogger implements Logger {
 public function log($message) {
 /** noop **/
 }
 }
  • 21. DON'T IMPLEMENT THIS NULL LOGGER... ...BECAUSE THERE'S ONE BUILT INTO PSR/LOG
  • 22. TRAITS + SETTER INJECTION Add setter to trait Import trait into classes Implement interface on classes Configure DI container to setter-inject based on interface e.g. PsrLog{LoggerAwareInterface, LoggerAwareTrait} Sorry, you can't have an interface implement a trait directly.
  • 23. Parameter Injection public function __invoke(
 SlimHttpRequest $request,
 SlimHttpResponse $response,
 array $args = [])
 { /** do something, return a Response **/ }
  • 24. Parameter Injection public function orderAction(
 IlluminateHttpRequest $request,
 AppServicesOrderService $orderService)
 { /** do something, return a Response **/ }
  • 25. Pros Does not clutter your object with refs to collaborators which are not needed
 CONS Almost everything else about it Moves the problem of dependency management to the caller
  • 26. Mix & Match as Needed
  • 27. Dependency Inversion Principle High level modules should not depend on low level modules; both should depend on abstractions. Abstractions should not depend on details.  Details should depend upon abstractions. Tl;dr: use, and expose, interfaces with just enough functionality to get the job done.
  • 28. Abstrations Should Not Be Leaky class Camry implements HasGasPedal { public function pressAccelerator(); } // namespace ToyotaVehicles class Model3 implements HasGasPedal { public function pressAccelerator(); } // namespace TeslaVehicles
  • 29. Abstrations Should Not Be Leaky class MysqlUserRepo implements UserRepository {
 public function getById(int $id): ?User {}
 } class ElasticUserRepo implements UserRepository {
 public function getById(int $id): ?User {}
 } class gRPCUserAPI implements UserRepository {
 public function getById(int $id): ?User {}
 } interface User { /** various signatures **/ }
  • 30. Let’s Do Some Refactoring class OrderProcessor {
 function __construct() {
 $this->orderRepository = new MysqlOrderRepository();
 } function completeOrder($order) {
   global $logger;
 $order->completedAt = new DateTimeImmutable;
 $logger->log("Order {$order->id} is complete");
 $this->orderRepository->save($order);
 Mailer::sendOrderCompleteEmail($order);
 }
 }
  • 31. Let’s Do Some Refactoring class OrderProcessor {
 function __construct(OrderRepository $orderRepo, Logger $logger) {
 $this->orderRepository = $orderRepo;
 $this->logger = $logger;
 } function completeOrder($order) {
 $order->completedAt = new DateTimeImmutable;
 $this->logger->log(“Order {$order->id} is complete");
 $this->orderRepository->save($order);
 Mailer::sendOrderCompleteEmail($order);
 }
 }
  • 32. Let’s Do Some Refactoring class OrderProcessor {
 function __construct(OrderRepository $orderRepo, Logger $logger,
 DateTimeImmutable $now, Mailer $mailer) {
 $this->orderRepository = $orderRepo;
 $this->logger = $logger;
 $this->now = $now;
 $this->mailer = $mailer;
 } function completeOrder($order) {
 $order->completedAt = $this->now;
 $this->logger->log(“Order {$order->id} is complete");
 $this->orderRepository->save($order);
 $this->mailer->sendOrderCompleteEmail($order);
 }
 }
  • 33. Let’s Do Some Refactoring class OrderProcessor {
 function __construct(OrderRepository $orderRepo, Logger $logger,
 Mailer $mailer) {
 $this->orderRepository = $orderRepo;
 $this->logger = $logger;
 $this->now = $now;
 } function completeOrder($order, DateTimeImmutable $now) {
 $order->completedAt = $now;
 $this->logger->log(“Order {$order->id} is complete");
 $this->orderRepository->save($order);
 $this->mailer->sendOrderCompleteEmail($order);
 }
 }
  • 34. What is a DI Container? A dependency injection container is an object used to manage the instantiation of other objects. If you have one, it will be the place where the "new" keyword gets used more than anywhere else in your app, either via configuration code you write or under the hood.
  • 35. What a DI Container is NOT Not the only place you can (or should) use the "new" keyword in your application. Factories Value objects Not required for dependency injection. Not to be used as a Service Locator
  • 36. PSR-11(FKA Container-Interop) namespace PsrContainer; interface ContainerInterface {
 public function get($id);
 public function has($id);
 }
  • 37. Twittee: A DI Container in a 140 Tweet class Container {
 protected $s=array();
 function __set($k, $c) { $this->s[$k]=$c; }
 function __get($k) { return $this->s[$k]($this); }
 }
  • 38. Using Twitter class NeedsALogger {
   private $logger;
 public function __construct(Logger $logger) {
   $this->logger = $logger;
   }
 }
 class Logger {}
 $c = new Container;
 $c->logger = function() { return new Logger; };
 $c->myService = function($c) {
 return new NeedsALogger($c->logger);
 };
 var_dump($c->myService); // includes the Logger
  • 39. Twittee++: A PSR-11 Container in a 280 Tweet class Container implements PsrContainerContainerInterface {
 protected $s=array();
 function __set($k, $c) { $this->s[$k]=$c; }
 function __get($k) { return $this->s[$k]($this); }
 function get($k) { return $this->s[$k]($this); }
 function has($k) { return isset($this->s[$k]); }
 }
  • 40. Full DI Containers Every major framework has one Symfony (DependencyInjection component) Zend (ServiceManager) Laravel (IlluminateContainer) Standalone ones for use elsewhere Pimple (used in Slim) LeagueContainer Aura.Di Disco
  • 42. Antipattern #2” Service Location class OrderProcessor {
 function __construct(Container Interface $c) {
 $this->orderRepository = $c->get(‘OrderRepository’);
 $this->logger = $c->get(‘Logger’);
 $this->mailer = $c->get(‘Mailer’);
 } function completeOrder($order, DateTimeImmutable $now) {
 $order->completedAt = $now;
 $this->logger->log(“Order {$order->id} is complete");
 $this->orderRepository->save($order);
 $this->mailer->sendOrderCompleteEmail($order);
 }
 }
  • 43. Antipattern #2” Service Location class OrderProcessor {
 protected $c;
 function __construct(Container Interface $c) {
 $this->orderRepository = $c->get(‘OrderRepository’);
 $this->c = $c;
 } function completeOrder($order, DateTimeImmutable $now) {
 $order->completedAt = $now;
 $this->c->get(‘Logger’)—>log(“Order {$order->id} is complete");
 $this->orderRepository->save($order);
 $this->c->get(‘Mailer’)—>sendOrderCompleteEmail($order);
 }
 }
  • 44. Using a Container In a typical application you will use the container from within your “controllers” and use them to inject dependencies into your “models”.
  • 45. Pimple - PSR-11 Compliant $c = new PimpleContainer; $c[NeedsALogger::class] = function($c) {
 return new NeedsALogger($c['logger']);
 }; $c['logger'] = function() {
 return new Logger;
 }; var_dump($c[NeedsALogger::class]); // NeedsALogger
  • 46. Pimple Use $c->factory(callable) if you don't want the same instance every time you ask for a dependency. Use $c->protect(callable) if you want to add a closure to your container. Use $c->raw(dep) if you want to get a closure you set without using protect() Not much magic Default container of Slim 3
  • 47. Refactor a Slim Route $app = new SlimApp(); $app->get('/', function(Request $req, Response $res) {
 $userRepository = new UserRepository(new PDO(/* */));
 $users = $userRepository->listAll();
 return $res->withJson($users);
 });
  • 48. Refactor a Slim Route $app = new SlimApp();
 $c = $app->getContainer();
 $c['db'] = function($c) { return new PDO(/* */); }; $app->get('/', function(Request $req, Response $res) use ($c) {
 $userRepository = new UserRepository($c[‘db’]);
 $users = $userRepository->listAll();
 return $res->withJson($users);
 });
  • 49. Refactor a Slim Route $app = new SlimApp();
 $c = $app->getContainer();
 $c['db'] = function($c) { return new PDO(/* */); };
 $c['userRepository'] = function($c) {
 return new UserRepository($c['db']);
 }; $app->get('/', function(Request $req, Response $res) {
 $userRepository = $this->get(‘userRepository');
 $users = $userRepository->listAll();
 return $res->withJson($users);
 });
  • 50. Refactor a Slim Route class GetUsersAction implements SlimControllerInterface {
 protected $userRepository; public function __construct(UserRepository $repo) {
 $this->userRepository = $repo;
 } public function __invoke(Request $req, Response $res) {
 $users = $this->userRepository->listAll();
 return $res->withJson($users);
 }
 };
 };
  • 51. Refactor a Slim Route $app = new SlimApp();
 $c = $app->getContainer();
 $c['db'] = function($c) { return new PDO(/* */); };
 $c['userRepository'] = function($c) {
 return new UserRepository($c['db']);
 };
 $c['getUsers'] = function($c) {
 return new GetUsersAction(UserRepository $c['userRepository']);
 }; $app->get('/', 'getUsers');
  • 52. Recap Dependencies in code are unavoidable, but that doesn’t mean they need to be unmanageable Inverting dependencies is a way to create more flexible software DI containers are a helpful tool for maintaining single responsibility within objects
  • 54. Alena Holligan • Wife, and Mother of 3 young children • PHP Teacher at Treehouse • Portland PHP User Group Leader • Cascadia PHP Conference (cascadiaphp.com) @alenaholligan alena@holligan.us https://joind.in/talk/b024a