Slide 1

Slide 1 text

DEPENDENCY INJECTION Entenda pra nunca mais esquecer!

Slide 2

Slide 2 text

Sou Junior Grossi twitter.com/junior_grossi github.com/jgrossi

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

PACIÊNCIA!

Slide 5

Slide 5 text

https://github.com/corcel/corcel

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

https://www.glofox.com/careers

Slide 9

Slide 9 text

AGENDA Dependency Injection Dependency Inversion (SOLID) Dependency Injection Container

Slide 10

Slide 10 text

DEPENDENCY INJECTION?

Slide 11

Slide 11 text

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/

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

OBSERVAÇÕES Agora, para instanciar AvatarRequestUploader precisamos injetar AwsS3Client A classe AvatarRequestUploader agora depende da AwsS3Client

Slide 16

Slide 16 text

DEPENDENCY INJECTION NOVA CLASSE UPLOADER? $s3Client = new AwsS3Client([ /* parameters */]); $avatarRequestUploader = new AvatarRequestUploader($s3Client); $avatarRequestUploader­>upload($request); $avatarMiddlewareUploader = new AvatarMiddlewareUploader($s3Client); $avatarMiddlewareUploader­>upload($request);

Slide 17

Slide 17 text

Dependency Injection é basicamente mover todas as dependências para o __construct

Slide 18

Slide 18 text

FORMAS DE INJETAR DEPENDÊNCIAS Constructor Injection Setter Injection Method Injection

Slide 19

Slide 19 text

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; } }

Slide 20

Slide 20 text

SETTER INJECTION ⚠ Muita permissão / vulnerabilidade! class Authorizer { private Logger $logger; public function setLogger(Logger $logger) { $this­>logger = $logger; } }

Slide 21

Slide 21 text

METHOD INJECTION Processamento de dados de input / request. class UserController { public function create(SaveUserRequest $request) { $data = $request­>validated(); // Code... } }

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

DEPENDENCY INVERSION? (SOLID) "Dependency Inversion Principle (DIP)"

Slide 24

Slide 24 text

Robert C. Martin (Uncle Bob) Design Principles and Design Patterns "Depend upon Abstractions. Do not depend upon concretions." https://bit.ly/2W6XAVm/

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Agora também precisamos fazer upload dos avatars para o Dropbox.

Slide 27

Slide 27 text

AwsS3Client é uma classe concreta DropboxClient é uma classe concreta Solução? CloudStorageInterface (abstração) Abstração = Interface

Slide 28

Slide 28 text

interface CloudStorageInterface { public function store(string $content): string; }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

(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

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

DEPENDENCY INJECTION CONTAINER "Livro de Receita" Fonte: https://bit.ly/2ExbFDS

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

PSR-11: CONTAINER INTERFACE - PHP-FIG Psr\Container\ContainerInterface Métodos: get() e has() Psr\Container\ContainerExceptionInterface Psr\Container\NotFoundExceptionInterface

Slide 39

Slide 39 text

league/container A SIMPLE BUT POWERFUL PSR-11 DEPENDENCY INJECTION CONTAINER http://container.thephpleague.com

Slide 40

Slide 40 text

namespace Acme; class Foo { public Bar $bar; public function __construct(Bar $bar) { $this­>bar = $bar; } } class Bar {}

Slide 41

Slide 41 text

$container = new League\Container\Container; $container­>add(Acme\Foo::class)­>addArgument(Acme\Bar::class); $container­>add(Acme\Bar::class); $foo = $container­>get(Acme\Foo::class); var_dump($foo instanceof Acme\Foo); // true var_dump($foo­>bar instanceof Acme\Bar); // true

Slide 42

Slide 42 text

Service Provider não é exclusivo do Laravel $container = new League\Container\Container; $container­>addServiceProvider( Acme\ServiceProvider\SomeServiceProvider::class ); $foo = $container­>get(Acme\Foo::class);

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

OUTROS PACOTES illuminate/container pimple/pimple php­di/php­di https://github.com/illuminate/container https://github.com/silexphp/Pimple https://github.com/PHP-DI/PHP-DI

Slide 47

Slide 47 text

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ê?)

Slide 48

Slide 48 text

BÔNUS Testes usando DI

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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, ], ];

Slide 52

Slide 52 text

USE DI E DI CONTAINER Vale a pena! Seu projeto agradece!

Slide 53

Slide 53 text

OBRIGADO Perguntas? Dúvidas? Críticas? Sugestões? twitter.com/junior_grossi github.com/jgrossi grossi.dev