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

Dependency Injection: Entenda pra nunca mais es...

Dependency Injection: Entenda pra nunca mais esquecer! 🇧🇷

Dependency Injection em PHP já é muito utilizado, principalmente pelos frameworks mais modernos.
Porém, a essência de um container, seu funcionamento, e como ele pode ajudar seu projeto, principalmente para testes, são ainda muito "mágicos" para os devs em geral.
Esta palestra tem por objetivo explicar de forma bem prática o que é e pra que serve DI.

Gravação da palestra:
Parte 1: https://www.youtube.com/watch?v=S2Dl1F4L3MY
Parte 2: https://www.youtube.com/watch?v=pur7LmtrUL0

Junior Grossi

May 17, 2019
Tweet

More Decks by Junior Grossi

Other Decks in Programming

Transcript

  1. Thorben Janssen "Dependency injection is a programming technique that makes

    a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLID’s dependency inversion and single responsibility principles." https://stackify.com/dependency-injection/
  2. class AvatarRequestUploader { private AwsS3Client $s3Client; // PHP 7.4 public

    function __construct() { $this­>s3Client = new AwsS3Client( // credentials + configs ); } public function upload(Request $request): string { $avatar = $this­>findAvatarInRequest($request); $avatarUrl = $this­>s3Client­>store($avatar); return $avatarUrl; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  3. public function __construct() { $this­>s3Client = new AwsS3Client( // credentials

    + configs ); } class AvatarRequestUploader 1 { 2 private AwsS3Client $s3Client; // PHP 7.4 3 4 5 6 7 8 9 10 11 public function upload(Request $request): string 12 { 13 $avatar = $this­>findAvatarInRequest($request); 14 $avatarUrl = $this­>s3Client­>store($avatar); 15 16 return $avatarUrl; 17 } 18 } 19
  4. public function __construct(AwsS3Client $s3Client) { $this­>s3Client = $s3Client; } class

    AvatarRequestUploader 1 { 2 private AwsS3Client $s3Client; 3 4 5 6 7 8 9 public function upload(Request $request): string 10 { 11 $avatar = $this­>findAvatarInRequest($request); 12 $avatarUrl = $this­>s3Client­>store($avatar); 13 14 return $avatarUrl; 15 } 16 } 17
  5. DEPENDENCY INJECTION NOVA CLASSE UPLOADER? $s3Client = new AwsS3Client([ /*

    parameters */]); $avatarRequestUploader = new AvatarRequestUploader($s3Client); $avatarRequestUploader­>upload($request); $avatarMiddlewareUploader = new AvatarMiddlewareUploader($s3Client); $avatarMiddlewareUploader­>upload($request);
  6. CONSTRUCTOR INJECTION ✅ Forma mais aconselhada de DI. class A

    { private Foo $foo; private Bar $bar; public function __construct(Foo $foo, Bar $bar) { $this­>foo = $foo; $this­>bar = $bar; } }
  7. SETTER INJECTION ⚠ Muita permissão / vulnerabilidade! class Authorizer {

    private Logger $logger; public function setLogger(Logger $logger) { $this­>logger = $logger; } }
  8. METHOD INJECTION Processamento de dados de input / request. class

    UserController { public function create(SaveUserRequest $request) { $data = $request­>validated(); // Code... } }
  9. Robert C. Martin (Uncle Bob) Design Principles and Design Patterns

    "Depend upon Abstractions. Do not depend upon concretions." https://bit.ly/2W6XAVm/
  10. public function __construct(AwsS3Client $s3Client) { $this­>s3Client = $s3Client; } class

    AvatarRequestUploader 1 { 2 private AwsS3Client $s3Client; 3 4 5 6 7 8 9 public function upload(Request $request): string 10 { 11 $avatar = $this­>findAvatarInRequest($request); 12 $avatarUrl = $this­>s3Client­>store($avatar); 13 14 return $avatarUrl; 15 } 16 } 17
  11. AwsS3Client é uma classe concreta DropboxClient é uma classe concreta

    Solução? CloudStorageInterface (abstração) Abstração = Interface
  12. class MyAwsS3Client implements CloudStorageInterface { private AwsS3Client $client; public function

    __contruct(AwsS3Client $client) { $this­>client = $client; } public function store(string $content): string { return $this­>client­>store($content); } }
  13. class MyDropboxClient implements CloudStorageInterface { private DropboxClient $client; public function

    __contruct(DropboxClient $client) { $this­>client = $client; } public function store(string $content): string { $name = $this­>generateRandomName(); $result = $this­>client­>send($name, $content); if (!$result) { throw new CloudStorageUploadException(); } return $this­>getUrlFor($name); } }
  14. public function __construct(CloudStorageInterface $cloudStorage) { $this­>cloudStorage = $cloudStorage; } $avatarUrl

    = $this­>cloudStorage­>store($avatar); class AvatarRequestUploader 1 { 2 private CloudStorageInterface $cloudStorage; 3 4 5 6 7 8 9 public function upload(Request $request): string 10 { 11 $avatar = $this­>findAvatarInRequest($request); 12 13 14 return $avatarUrl; 15 } 16 } 17
  15. (new AvatarRequestUploader($myS3Client)) ­>upload($request); (new AvatarRequestUploader($mydropboxClient)) ­>upload($request); $s3Client = new AwsS3Client([

    /* parameters */]); 1 $myS3Client = new MyAwsS3Client($s3Client); 2 3 4 5 6 $dropboxClient = new DropboxClient([ /* parameters */]); 7 $mydropboxClient = new MyDropboxClient($dropboxClient); 8 9 10 11
  16. PROCESSO Você "ensina" ao container como criar seus objetos, informando

    suas dependências. Você solicita uma instância de um objeto ao Container. O Container sabe como instanciar, e então te retorna a instância desejada.
  17. Basta pedir ao Container: $s3Client = new AwsS3Client([ /* parameters

    */]); $myS3Client = new MyAwsS3Client($s3Client); (new AvatarRequestUploader($myS3Client)) ­>upload($request); $container = Container::instance(); $avatarUploader = $container­>get(AvatarRequestUploader::class); $avatarUploader­>upload($request);
  18. Ou utilizar como dependência de outra classe: class ChangeAvatarAction {

    private AvatarRequestUploader $avatarUploader; public function __construct(AvatarRequestUploader $avatarUploader) { $this­>avatarUploader = $avatarUploader; } public function __invoke(ServerRequestInterface $request): ResponseInterface { $avatarUrl = $this­>avatarUploader­>upload($request); return new JsonResponse([ 'avatar' => $avatarUrl, ], 201); } }
  19. PSR-11: CONTAINER INTERFACE - PHP-FIG Psr\Container\ContainerInterface Métodos: get() e has()

    Psr\Container\ContainerExceptionInterface Psr\Container\NotFoundExceptionInterface
  20. namespace Acme; class Foo { public Bar $bar; public function

    __construct(Bar $bar) { $this­>bar = $bar; } } class Bar {}
  21. Service Provider não é exclusivo do Laravel $container = new

    League\Container\Container; $container­>addServiceProvider( Acme\ServiceProvider\SomeServiceProvider::class ); $foo = $container­>get(Acme\Foo::class);
  22. class SomeServiceProvider extends AbstractServiceProvider { protected array $provides = [

    Acme\Foo::class, Acme\Bar::class, ]; public function register(): void { $container = $this­>getContainer(); $container­>add(Acme\Foo::class)­>addArgument(Acme\Bar::class); $container­>add(Acme\Bar::class); } }
  23. Auto wiring também não é exclusivo do Laravel Funciona apenas

    para objetos! $container = new League\Container\Container; $container­>delegate( new League\Container\ReflectionContainer ); $foo = $container­>get(Acme\Foo::class);
  24. Atenção: evite usar o Container como dependência ⚠ É muita

    liberdade! Pode ferir SRP! class Foo { private Bar $foo; private Baz $baz; public function __construct(ContainerInterface $container) { $this­>bar = $container­>get(Bar::class); $this­>baz = $container­>get(Baz::class); } }
  25. CONSIDERAÇÕES SOBRE DI Permite flexibilização da arquitetura Código plugável (troca

    de componentes mais fácil) Centralização de objetos (pra quê?)
  26. Unit Test public function test_avatar_can_be_uploaded_through_the_request(): void { // $s3Client =

    new AwsS3Client([ /* parameters */]); // $myS3Client = new MyAwsS3Client($s3Client); $cloudStorageMock = \Mockery::mock(CloudStorageInterface::class); $cloudStorageMock­>shoudReceive('store')­>andReturn('http://avatar.com'); $uploader = new AvatarRequestUploader($cloudStorageMock); $avatarUrl = $uploader­>upload(new Request(['avatar' => 'foo'])); $this­>assertEquals('http://avatar.com', $avatarUrl); }
  27. Integration/Feature Test public function test_user_can_change_avatar(): void { $cloudStorageMock = \Mockery::mock(CloudStorageInterface::class);

    $cloudStorageMock­>shoudReceive('store')­>andReturn('http://avatar.com'); $container = Container::instance(); $container­>replace(CloudStorageInterface::class, $cloudStorageMock); $response = $this­>json('PATCH', '/1.0/users/change­avatar', [ 'avatar' => 'foo', ]); $result = $response­>getContent()­>toArray(); $this­>assertEquals('http://avatar.com', $result['avatar']); }
  28. Testando Events / Listeners > Feature Test: POST /1.0/users private

    array $events = [ Events\UserWasCreated::class => [ Listeners\SendWelcomeEmail::class, Listeners\RegisterUserAtIntercom::class, Listeners\UploadAvatarIfPresent::class, Listeners\SendWelcomeSlackNotification::class, ], ];