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

Laravel でやってみるクリーンアーキテクチャ #phpconfuk

Laravel でやってみるクリーンアーキテクチャ #phpconfuk

ドメイン駆動設計やユースケース駆動開発などの文脈でレイヤードアーキテクチャやクリーンアーキテクチャといった言葉をよく聞くようになりました。
「名前は聞いたことあるけど、敷居が高そう......」「本は読んだけど実際に実装するイメージがつかない......」そんなことを感じている方もいらっしゃるのではないでしょうか?
本トークではそんな方に向けて、簡単なアプリケーションの実装例とともに、クリーンアーキテクチャの考え方や実装する上での Laravel の機能についてお話します!

2018/06/29 開催の PHP カンファレンス福岡2019 (https://phpcon.fukuoka.jp/2019/) の発表資料です。

Shohei Okada

June 28, 2019
Tweet

More Decks by Shohei Okada

Other Decks in Programming

Transcript

  1. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 3
  2. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 4
  3. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 7
  4. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 13
  5. యܕతͳґଘؔ܎ͷίʔυ package foo; import bar; class Foo { bar.Bar x;

    function doFoo() { x.process(); } } 23 package bar; class Bar { function process() { // do something... } }
  6. యܕతͳґଘؔ܎ͷίʔυ package foo; import bar; class Foo { bar.Bar x;

    function doFoo() { x.process(); } } 24 package bar; class Bar { function process() { // do something... } } foo ͔Β bar ͷॲཧΛݺͼग़͢ = ॲཧͷํ޲ foo → bar
  7. యܕతͳґଘؔ܎ͷίʔυ package foo; import bar; class Foo { bar.Bar x;

    function doFoo() { x.process(); } } 25 package bar; class Bar { function process() { // do something... } } foo ͕ bar ͷ͜ͱΛ஌͍ͬͯΔ = ґଘͷํ޲ foo → bar
  8. యܕతͳґଘؔ܎ͷίʔυ package foo; import bar; class Foo { bar.Bar x;

    function doFoo() { x.process(); } } 26 package bar; class Bar { function process() { // do something... } } ॲཧͷํ޲ foo → bar ґଘͷํ޲ foo → bar
  9. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 27 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  10. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 28 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  11. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 29 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } foo ͔Β bar ͷॲཧΛݺͼग़͢ = ॲཧͷํ޲ foo → bar
  12. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 30 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } bar ͕ foo ͷ͜ͱΛ஌͍ͬͯΔ = ґଘͷํ޲ foo ← bar
  13. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 31 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } ॲཧͷํ޲ foo → bar ґଘͷํ޲ foo ← bar
  14. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 35
  15. ϑϨʔϜϫʔΫʹґଘͤ͞ͳ͍ • ґଘͤ͞Δͷ͸ PHP ૊ΈࠐΈͷΫϥεఔ౓ 42 <?php namespace MyApp\Components\Tasks\Entities; use

    DatetimeImmutable; /** * Class Inbox * @package MyApp\Components\Tasks\Entities */ final class Inbox extends Task { /** * @var EstimatedTime|null */ private $estimatedTime;
  16. Ϣχοτςετ • ґଘͷແ͍७ਮͳϩδοΫͳͷͰॻ͖΍͍͢ • நग़ͨ͠Ϗδωεϧʔϧͷจݴ͕ͦͷ··ς ετέʔεʹͳΔ 43 /** * @test

    * @expectedException \MyApp\Components\Tasks\Entities\EstimatedTimeNotSet */ public function 「Inbox」に対して「見積もり時間」が未設定のまま「着手日」を設定できないこと() { $id = Mockery::mock(Id::class); $name = new Name('test'); $inbox = new Inbox($id, $name); $startDate = new StartDate(new DateTimeImmutable('tomorrow')); $inbox->convertToScheduled($startDate); }
  17. InputBoundary / OutputBoundary • ೖྗʢग़ྗʣσʔλΛҾ਺ʹऔΔ
 ݺͼग़͠ՄೳͳΠϯλʔϑΣʔε 47 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox;

    /** * Interface InputBoundary * @package MyApp\Components\Tasks\UseCases\CreateInbox */ interface InputBoundary { /** * @param InputData $input */ public function __invoke(InputData $input): void; }
  18. InputBoundary / OutputBoundary • ೖྗʢग़ྗʣσʔλΛҾ਺ʹऔΔ
 ݺͼग़͠ՄೳͳΠϯλʔϑΣʔε 48 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox;

    /** * Interface NormalOutputBoundary * @package MyApp\Components\Tasks\UseCases\CreateInbox */ interface NormalOutputBoundary { /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void; }
  19. Interactor 49 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox; use MyApp\Components\Tasks\Entities\{Inbox, Task, Name, Note};

    use MyApp\Components\Tasks\UseCases\{IdProvider, TaskRepository}; /** * Class Interactor * @package MyApp\Components\Tasks\UseCases\CreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task
  20. Interactor 50 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox; use MyApp\Components\Tasks\Entities\{Inbox, Task, Name, Note};

    use MyApp\Components\Tasks\UseCases\{IdProvider, TaskRepository}; /** * Class Interactor * @package MyApp\Components\Tasks\UseCases\CreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task InputBoundary ͷ࣮૷
  21. Interactor 51 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox; use MyApp\Components\Tasks\Entities\{Inbox, Task, Name, Note};

    use MyApp\Components\Tasks\UseCases\{IdProvider, TaskRepository}; /** * Class Interactor * @package MyApp\Components\Tasks\UseCases\CreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task ґଘੑ͸֎෦͔Β஫ೖʢDIʣ ※ґଘͯ͠Δ΋ͷΛϞοΫԽͯ͠
 ɹςετ͕ॻ͖΍͍͢
  22. Interactor 52 <?php namespace MyApp\Components\Tasks\UseCases\CreateInbox; use MyApp\Components\Tasks\Entities\{Inbox, Task, Name, Note};

    use MyApp\Components\Tasks\UseCases\{IdProvider, TaskRepository}; /** * Class Interactor * @package MyApp\Components\Tasks\UseCases\CreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task ॲཧຊମ
 ʢ࣍εϥΠυʹղઆଓ͘ʣ
  23. Interactor::__invoke() ೖྗσʔλ͔Β
 λεΫΛੜ੒ 53 /** * @param InputData $input */

    public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  24. Interactor::__invoke() ੜ੒ͨ͠λεΫΛ
 ӬଓԽ 54 /** * @param InputData $input */

    public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  25. Interactor::__invoke() ॲཧ݁Ռ͔Β
 ग़ྗσʔλΛੜ੒ 55 /** * @param InputData $input */

    public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  26. Interactor::__invoke() OutputBoundary Λ
 ݺͼग़͠ 56 /** * @param InputData $input

    */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  27. WebʗUI • ϢʔεέʔεͰఆٛͨ͠ΠϯλʔϑΣʔεΛ࣮૷ • ਤʹ஧࣮ʹ΍ΔͳΒ Controller ϝιουͷ
 ໭Γ஋͸ void •

    ͕ɺ͜ͷܗʹ͸ͦ͜·Ͱͩ͜ΘΒͳͯ͘Α͍
 ʢมଇతͳΧελϚΠζ͕ඞཁʹͳΔʣ 60
  28. Controller 61 <?php namespace MyApp\Web\Controllers; use Illuminate\Http\Request; use MyApp\Components\Tasks\UseCases\CreateInbox\InputData; use

    MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; /** * Class CreateInbox * @package MyApp\Web\Controllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } }
  29. Controller 62 <?php namespace MyApp\Web\Controllers; use Illuminate\Http\Request; use MyApp\Components\Tasks\UseCases\CreateInbox\InputData; use

    MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; /** * Class CreateInbox * @package MyApp\Web\Controllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } Laravel ͷ Http\Controllers ͱಉ͡Α͏ʹॻ͚Δ
  30. Controller 63 <?php namespace MyApp\Web\Controllers; use Illuminate\Http\Request; use MyApp\Components\Tasks\UseCases\CreateInbox\InputData; use

    MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; /** * Class CreateInbox * @package MyApp\Web\Controllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } HTTPϦΫΤετˠ७ਮͳೖྗσʔλʹม׵
  31. Controller 64 <?php namespace MyApp\Web\Controllers; use Illuminate\Http\Request; use MyApp\Components\Tasks\UseCases\CreateInbox\InputData; use

    MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; /** * Class CreateInbox * @package MyApp\Web\Controllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } InputBoundaryʢ࣮ମ͸ InteractorʣΛݺͼग़͢
  32. Controller 65 <?php namespace MyApp\Web\Controllers; use Illuminate\Http\Request; use MyApp\Components\Tasks\UseCases\CreateInbox\InputData; use

    MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; /** * Class CreateInbox * @package MyApp\Web\Controllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } }
  33. Presenter • આ໌ 66 <?php namespace MyApp\Web\Presenters\CreateInbox; use Illuminate\View\Factory; use

    MyApp\Web\Presenters\Presenter as BasePresenter; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputData; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyApp\Web\Presenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } }
  34. Presenter • આ໌ 67 <?php namespace MyApp\Web\Presenters\CreateInbox; use Illuminate\View\Factory; use

    MyApp\Web\Presenters\Presenter as BasePresenter; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputData; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyApp\Web\Presenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } } Interactor Ͱੜ੒͞Εͨग़ྗσʔλΛ ViewModel ʹม׵
  35. Presenter • આ໌ 68 <?php namespace MyApp\Web\Presenters\CreateInbox; use Illuminate\View\Factory; use

    MyApp\Web\Presenters\Presenter as BasePresenter; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputData; use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyApp\Web\Presenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } } View Λੜ੒ͯ͠ϨεϙϯεΛฦ͢ ※มͳ͜ͱ΍ͬͯΔͷ͸ Controllerͷ໭Γ஋Λ
 void ʹ͢ΔͨΊͳͷͰؾʹ͠ͳ͍Ͱ……🙇
  36. 70 <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use MyApp\Components\Tasks\UseCases\CreateInbox\InputBoundary; use MyApp\Components\Tasks\UseCases\CreateInbox\Interactor;

    use MyApp\Components\Tasks\UseCases\CreateInbox\NormalOutputBoundary; use MyApp\Components\Tasks\UseCases\IdProvider; use MyApp\Components\Tasks\UseCases\TaskRepository as TaskRepositoryInterface; use MyApp\Database\Repositories\AutoIncrementTaskIdProvider; use MyApp\Database\Repositories\TaskRepository; use MyApp\Web\Presenters\CreateInbox\Presenter; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->bind(IdProvider::class, AutoIncrementTaskIdProvider::class); $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); $this->app->bind(InputBoundary::class, Interactor::class); $this->app->bind(NormalOutputBoundary::class, Presenter::class); } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }
  37. Laravel ͱͷ෇͖߹͍ํ • Laravel ͷػೳΛ࢖͏ͳΒ֎ଆͷ૚Ͱ࢖͏ • ڧྗͳ DI ͷ࢓૊Έʹ৐͔ͬΔʢServiceContainerʣ •

    ύοέʔδʹ෼͚ͯ։ൃͯ͠
 ServiceProvider ͰͦΕΒΛ౷߹͢Δ • ී௨ʹॻ͘Ҏ্ʹґଘؔ܎Λҙࣝ͢ΔͷͰ
 ૬ੑ͕͍͍ؾ͕͍ͯ͠Δ 71
  38. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 78
  39. ґଘؔ܎ٯసͨ͠ίʔυ package foo; class Foo { Buz x; function doFoo()

    { x.process(); } } interface Buz { function process(); } 80 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  40. ໨࣍ • ͜ͷτʔΫͷΊ͟͢ͱ͜Ζʢ2 ෼ʣ • ΫϦʔϯΞʔΩςΫνϟͷ֓ཁʢ3 ෼ʣ • ίΞͱͳΔߟ͑ํʢ5 ෼ʣ

    • ۩ମతͳίʔυྫʢ10 ෼ʣ • ·ͱΊʢ3 ෼ʣ • ࣗݾ঺հʢ2 ෼ʣ 82