Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency inversion, injection and the containers

Dependency inversion, injection and the containers

In this talk, I'll talk about the dependency inversion principle, dependency injection and DI containers, using real-life examples for each of the concepts to understand the difference between them, how they align together and the benefits of applying it to your code.

This talk was given at the SydPHP May 2019 Meetup.

Luã de Souza

May 30, 2019
Tweet

Other Decks in Technology

Transcript

  1. Why should I care? - Helps decoupling your code -

    Promotes a clean and readable codebase - Your code becomes more reusable - Makes testing easier - Improves the logical abstraction of components
  2. <?php class CaptureCommand { public function execute(OutputInterface $output) { $fetcher

    = new StatsFetcher(); $summary = $fetcher->fetch(); $output->writeln($summary->getResult()); } } Sample class
  3. class CaptureCommand { private $fetcher; public function __construct(StatsFetcher $fetcher) {

    $this->fetcher = $fetcher; } public function execute(OutputInterface $output) { $summary = $this->fetcher->fetch(); // ... } } Constructor injection
  4. class CaptureCommand { private $logger; public function setLogger(LoggerInterface $logger) {

    $this->logger = $logger; } } Setter injection public function run() { if (!$this->logger instanceof LoggerInterface) { $this->logger->log('fetch'); } } }
  5. <?php class CaptureCommand { public function execute(OutputInterface $output) { $fetcher

    = new StatsFetcher(); $summary = $fetcher->fetch(); $output->writeln($summary->getResult()); } } Back to the sample class
  6. <?php class CaptureCommandTest extends TestCase { public function testCommandExecutionFecthesMyStats(): void

    { $fetcherMock = $this ->createMock(StatsFetcher::class); $fetcherMock->method('fetch')->willReturn([]); $command = new CaptureCommand($fetcherMock); // test only your class here } } Unit tests
  7. 1. High-level modules should not depend on low-level modules. Both

    should depend on abstractions. 2. Abstractions should not depend upon details. Details should depend upon abstractions. SOLID
  8. <?php // ShipmendAdapterInterface.php interface ShipmentAdapterInterface { public function create(Shipment $shipment):

    Shipment; } // Implementations class AuspostShipmentAdapter {} class StartrackShipmentAdapter {} Interfaces
  9. <?php class ShipmentClient extends AbstractClient { private $adapter; public function

    __construct(ShipmentAdapterInterface $adapter) { $this->adapter = $adapter; } public function create(Shipment $shipment): Shipment { return $this->adapter->create($shipment); } } Interfaces
  10. <?php abstract class AbstractFetcher { protected $connection; public function __construct(PDO

    $connection) { $this->connection = $connection; } abstract public function fetch(): Summary; } Abstract classes
  11. class CaptureCommand { private $fetcher; public function __construct( { $this->fetcher

    = $fetcher; } protected function execute(OutputInterface $output) { $summary = $this->fetcher->fetch(); // ... } } Abstract classes StatsFetcher $fetcher)
  12. class CaptureCommand { private $fetcher; public function __construct(AbastractFetcher $fetcher) {

    $this->fetcher = $fetcher; } protected function execute(OutputInterface $output) { $summary = $this->fetcher->fetch(); // ... } } Abstract classes
  13. <?php class GithubClient { private $httpClient; public function authenticate(string $token):

    void { $this->httpClient->request( 'POST', '/v1/authenticate', [ 'token' => $token, ] ); } } Base classes
  14. <?php class GithubReviewer { protected $githubClient; public function __construct(GithubClient $githubClient)

    { $this->githubClient = $githubClient; } public function auth(string $token): Reviewer { $this->githubClient->authenticate($token); return $this; } } Base classes
  15. Where is it applicable? - Frameworks - Dependency Injection containers

    - Event-driven architectures - Observer pattern
  16. <?php require __DIR__.'/../vendor/autoload.php'; $connection = new PDO('mysql:host=localhost;dbname=sample); $client = new

    GuzzleHttp\Client([]); $report = new Report($client, 'sample'); $fetcher = new StatsFetcher($connection, $report); $comand = new CaptureStatsCommand($fetcher); $app = new Application('Sample app'); $app->add($comand); $app->run(); Structured programming
  17. <?php class CaptureCommand { private $repository; public function __construct(RepositoryInterface $repository)

    { $this->repository = $repository; } public function run(): void { // ... } }
  18. <?php class StatsRepository implements RepositoryInterface { protected $connection; public function

    __construct(PDO $connection) { $this->connection = $connection; } // ... }
  19. <?php // container.php $container = new Container(); $container['connection'] = function

    () { return new PDO( sprintf('mysql:host=%s;dbname=sample', getenv('DB_HOST')) ); }
  20. <?php // container.php $container['repository.users'] = function (Container $container) { return

    new UsersRepository($container['connection']); } $container['model.payments'] = function (Container $container) { return new PaymentModel($container['connection']); }
  21. 1. Write clean, decoupled code 2. Make things reusable 3.

    Testability is key 4. Maintainable code is beautiful So why should I care?