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

[完全版]ServiceProvider, ServiceContainer入門

1439d17fca2c2d5318c6017f8cef657d?s=47 chiroruxx
February 16, 2019

[完全版]ServiceProvider, ServiceContainer入門

2019/02/16 Laravel JP Conferenceで話せなかった資料です。
98ページあります。

1439d17fca2c2d5318c6017f8cef657d?s=128

chiroruxx

February 16, 2019
Tweet

More Decks by chiroruxx

Other Decks in Technology

Transcript

  1. Service Provider Service Container 2/16 Laravel JP Conference 前田 和人

  2. こんな人におすすめ ▪ MVCは何となくわかる ▪ それ以外の機能はあまり知らない ▪ サービスコンテナを使ったことがない ▪ サービスコンテナで挫折した

  3. このセッションの目標 ▪ サービスコンテナをざっくり理解する ▪ サービスコンテナに1つのサービスを結合する勇気が出 る

  4. お話しないこと ▪ サービスコンテナの細かい仕様・実装 ▪ 豊富な実装例 ▪ サービスディスカバリ

  5. 注意事項 ▪ このセッションでは、理解することを最優先します ▪ 場合によっては正確ではないことを記載している場合が あります

  6. ▪ 時間の都合で削らせていただきます。すみません。 注意事項

  7. 注意事項 ▪ このセッションでは、コードがかなり出てきます。 ▪ 以下に資料があがってますので、 お手元で見ていただくことをおすすめします。

  8. 自己紹介 ▪ 前田 和人 ▪ @chiroruxxxx ▪ 弁護士ドットコム株式会社

  9. 自己紹介 ▪ 前田 和人 ▪ @chiroruxxxx ▪ 弁護士ドットコム株式会社

  10. 弁護士ドットコム

  11. BUSINESS LAWYERS

  12. 本題

  13. なぜサービスコンテナは難しいのか

  14. なぜサービスコンテナは難しいのか サービス DI サービス コンテナ インター フェース 自動注入

  15. サービス サービス DI サービス コンテナ インター フェース 自動注入

  16. ユースケース ▪ メール送信の例を考えてみましょう ▪ メール送信にはSendGridを使います – ユーザが登録した際に、 SendGrid側にユーザ情報を保持 – ユーザに登録完了メールを送信

    ▪ 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除
  17. ユースケース ▪ メール送信の例を考えてみましょう ▪ メール送信にはSendGridを使います – ユーザが登録した際に、 SendGrid側にユーザ情報を保持 – ユーザに登録完了メールを送信

    ▪ 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除
  18. ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient

    = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); }
  19. ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient

    = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化
  20. ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient

    = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成
  21. ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient

    = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成 ユーザ登録API
  22. ユーザ情報の登録のコード // …ユーザのDB保存処理 // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.key')); $recipient

    = new Recipient( $user->first_name, $user->last_name, $user->mail_address ); $response = $sendGrid->client->contactdb() ->recipients()->post([$recipient]); if (!starts_with($response->statusCode(), '20')) { Log::error('ユーザを登録できませんでした。'); } SDKを初期化 ユーザ情報を作成 ユーザ登録API エラーの場合は ログ出力
  23. メール送信のコード // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ',

    new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理
  24. メール送信のコード // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ',

    new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成
  25. メール送信のコード // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ',

    new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成 メール送信API
  26. メール送信のコード // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ',

    new Content('text/plain', '会員登録を受付ました。') ); $response = $sendGrid->client->mail()->send()->post($mail); if (!starts_with($response->statusCode(), '20')) { Log::error('メールを送信できませんでした。'); } // …ビューの表示処理 メール作成 メール送信API エラーの場合は ログ出力
  27. コードの状態 ▪ 見やすさ – 処理をざっと眺めるときにはわかりづらい ▪ ユーザ情報の保持とメール送信だけなのに長い

  28. コードの状態 ▪ 再利用性 – 「メールを送信する」という処理は色々な箇所で使 われそう – 毎回このコードをコピペする・・・?

  29. コードの状態 ▪ 保守性 – > 管理画面から選択したユーザを削除した際に、 SendGrid側のユーザ情報も削除 – 色々なところにSendGridのコードが出てくるのはつ らい

    – ライブラリの使い方が変わったら、修正箇所を洗い 出すだけでも大変
  30. コードの状態 ▪ つまり・・・? – 処理をべた書きすると、色々と大変そう ▪ ⇒サービス(クラス)をつくろう!

  31. サービスとは? ▪ 同じ枠組みの処理をひとつのクラスにまとめたもの – 今回は、SendGrid関連という枠組みでまとめる ▪ コンポーネントとも言う ▪ コントローラもモデルもサービス ▪

    (このセッション内だけの用語です)
  32. サービスのコード class SendGridService { private $sendGrid; public function __construct() {

    $this->sendGrid = new SendGrid(config('services.sendgrid.key’)); } public function saveUser(User $user): void { // 元のコントローラから処理をコピー } public function sendMail(Mail $mail): void { // 元のコントローラから処理をコピー } }
  33. コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user);

    // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理
  34. コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user);

    // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成
  35. コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user);

    // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録
  36. コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user);

    // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録 メール作成
  37. コントローラのコード // …ユーザーのDB保存処理 // sendgridのcontactに登録 $sendGridService = new SendGridService(); $sendGridService->saveUser($user);

    // 登録完了メール送信 $mail = new Mail( 'test@example.com', $user->mail_address, '会員登録受付のお知らせ', new Content('text/plain', '会員登録を受付ました。') ); $sendGridService->sendMail($mail); // …ビューの表示処理 サービス作成 ユーザ登録 メール作成 メール送信
  38. サービスのまとめ ▪ 見やすさ – 詳細な処理がコントローラに書かれていないので 見やすい ▪ 再利用性 – サービスのインスタンスを生成すれば

    コピペしなくて済む ▪ 保守性 – 似た処理が同じところに集まったので メンテナンスしやすい
  39. DI サービス DI サービス コンテナ インター フェース 自動注入

  40. ユースケース ▪ 今までSendGridのAPIキーは固定でした ▪ 統計上の都合で、ユーザ側と管理者側でAPIキーを分け ることになりました

  41. コードの状態 ▪ 今はサービスのコンストラクタにAPIキーをべた書き public function __construct() { $this->sendGrid = new

    SendGrid(config('services.sendgrid.key')); }
  42. コードの状態 ▪ UserSendGridクラスとAdminSendGridクラスに分け る・・・? – 設定の数だけクラスが増えていくの・・・? ▪ ⇒DIをしよう!

  43. DIとは? ▪ 何の略語かとか、日本語訳を載せると 途端に難しそうにきこえるアレ ▪ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ▪ 今回で言うと、SendGridクラス

  44. DIとは? ▪ 無駄にクラスを作成したり、setterメソッドが無くなる – setterが存在すると、 「このインスタンスの状態は?」 をずっと考えながらコーディングしないといけない ▪ インスタンスの設定を状況に合わせて指定できる

  45. サービスのコード class SendGridService { private $sendGrid; public function __construct(SendGrid $sendGrid)

    { $this->sendGrid = $sendGrid; } // …その他の処理 }
  46. サービスのコード class SendGridService { private $sendGrid; public function __construct(SendGrid $sendGrid)

    { $this->sendGrid = $sendGrid; } // …その他の処理 } 設定を受け取る
  47. ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->saveUser($user);
  48. ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成
  49. ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成
  50. ユーザー側のコントローラのコード // sendgridのcontactに登録 $sendGrid = new SendGrid(config('services.sendgrid.user.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->saveUser($user); 設定を生成 設定を渡す
  51. 管理者側のコントローラのコード // sendgridのcontactから削除 $sendGrid = new SendGrid(config('services.sendgrid.admin.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->deleteUsers([$id]);
  52. 管理者側のコントローラのコード // sendgridのcontactから削除 $sendGrid = new SendGrid(config('services.sendgrid.admin.key’)); $sendGridService = new

    SendGridService($sendGrid); $sendGridService->deleteUsers([$id]); 設定を生成 設定を渡す
  53. DIのまとめ ▪ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ▪ 設定を外から渡すことで、インスタンスの設定を状況に 合わせて指定できる

  54. サービス DI サービス コンテナ インター フェース 自動注入 サービスコンテナ サービスプロバイダ

  55. コードの状態 ▪ DIを使うと、サービスを使う側でnewする回数が増える ▪ 引数が3つや4つになったらその分だけnewしないといけない – 見通しが悪くなりそう ▪ サービスコンテナを使おう $sendGrid

    = new SendGrid('services.sendgrid.user.key'); $sendGridService = new SendGridService($sendGrid);
  56. サービスコンテナとは? ▪ サービスをDIするのをサポートするツール ▪ 様々なサービスを入れられるグローバルな配列 ▪ Laravelに出てくる「app」はサービスコンテナのこと key value sendgrid

    SendGridService ga GoogleAnalyticsService payment StripeService
  57. サービスプロバイダとは? ▪ サービスコンテナにデータを生成する方法を記述する場所 ▪ アプリケーションの初期化時にロードされ、 サービスコンテナに情報を入れていく ▪ コマンドで作成すると間違いがない – php

    artisan make:provider プロバイダ名 ▪ config/app.php で読み込むのを忘れないこと
  58. サービスプロバイダとは? サービスコンテナ サービスプロバイダ アプリケーション データ登録 データ取得

  59. サービスプロバイダとは? サービスコンテナ サービスプロバイダ アプリケーション register resolve

  60. サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() {

    $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } }
  61. サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() {

    $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } } key
  62. サービスプロバイダのコード class SendGridServiceProvider extends ServiceProvider { public function register() {

    $this->app->bind('sendgrid', function ($app) { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return new SendGridService($sendGrid); }); } } key value
  63. コントローラのコード // sendgridのcontactに登録 $sendGridService = resolve('sendgrid'); $sendGridService->saveUser($user); key

  64. コントローラのコード // sendgridのcontactに登録 $sendGridService = resolve('sendgrid'); $sendGridService->saveUser($user);

  65. サービスコンテナのまとめ ▪ DIを使うと、コントローラでnewがいっぱい出てくる ▪ サービスをDIするのをサポートするツール ▪ サービスコンテナはサービスの入ったグローバルな配列 ▪ サービスの生成方法についてはサービスプロバイダに 記述する

  66. サービス DI サービス コンテナ インター フェース 自動注入 自動注入

  67. Tips: 自動注入 ▪ サービスコンテナに登録するときのキーをクラス名にす ると、ちょっとラクに書けます! ▪ コントローラで引数に取ると、 そのままサービスが生成されます。 ▪ 自動注入されるサービスは、

    引数の型をもとに判断されます。
  68. サービスプロバイダのコード $this->app->bind(SendGridService::class, function () { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return

    new SendGridService($sendGrid); });
  69. サービスプロバイダのコード $this->app->bind(SendGridService::class, function () { $sendGrid = new SendGrid(config('services.sendgrid.user.key')); return

    new SendGridService($sendGrid); }); クラス名に
  70. コントローラのコード public function store(Request $request, SendGridService $sendGridService) { // …ユーザーのDB保存処理

    // sendgridのcontactに登録 $sendGridService->saveUser($user); // …メール送信 // …ビューの表示処理 }
  71. コントローラのコード public function store(Request $request, SendGridService $sendGridService) { // …ユーザーのDB保存処理

    // sendgridのcontactに登録 $sendGridService->saveUser($user); // …メール送信 // …ビューの表示処理 } アクションの引数で渡せる
  72. インターフェース

  73. ユースケース ▪ 開発環境の場合はSendGridに通信したくありません。 ▪ できれば送信されたメールの情報が ログに流れてほしいです。

  74. ユースケース ▪ ローカル環境では、SendGridServiceを使いたくない。 ▪ メールを送らないだけなので、やるべきことはほぼ同じ。 ▪ インターフェースを使いましょう

  75. インターフェースとは? ▪ クラスのやりとりの概要だけを表すもの ▪ 実際にどのように行うかは規定しない ▪ 今回だと、 – メール送信 –

    ユーザー作成 – ユーザー削除 ▪ が該当します。
  76. インターフェースとは? コントローラ SendGridService メール送ってね

  77. インターフェースとは? コントローラ SendGridService メール送ってね Interface Fake

  78. インターフェースのコード interface MailerService { function saveUser(User $user): void; function deleteUsers(array

    $users): void; function sendMail(Mail $mail): void; }
  79. SendGridServiceのコード class SendGridService implements MailerService { // …各処理 }

  80. SendGridServiceのコード class SendGridService implements MailerService { // …各処理 } インターフェース

    を設定
  81. FakeMailerServiceのコード class FakeMailerService implements MailerService { function saveUser(User $user): void

    { // …ログ出力処理 } function deleteUsers(array $users): void { // …ログ出力処理 } function sendMail(Mail $mail): void { // …ログ出力処理 } }
  82. FakeMailerServiceのコード class FakeMailerService implements MailerService { function saveUser(User $user): void

    { // …ログ出力処理 } function deleteUsers(array $users): void { // …ログ出力処理 } function sendMail(Mail $mail): void { // …ログ出力処理 } } インターフェース を設定
  83. MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() {

    if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } }
  84. MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() {

    if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } envが開発環境 だったら
  85. MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() {

    if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } MailerServiceに Fakeを設定
  86. MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() {

    if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } envが開発環境 でないなら
  87. MailerServiceProviderのコード class MailerServiceProvider extends ServiceProvider { public function register() {

    if (env('app_env') === 'local') { $this->app->bind(MailerService::class, function () { return new FakeMailerService(); }); } else { $this->app->bind(MailerService::class, function () { return resolve(SendGridService::class); }); } } } MailerServiceに SendGridを設定
  88. コントローラのコード public function store(Request $request, MailerService $mailerService) { // …ユーザーのDB保存処理

    // sendgridのcontactに登録 $mailerService->saveUser($user); // …ビューの表示処理 }
  89. コントローラのコード public function store(Request $request, MailerService $mailerService) { // …ユーザーのDB保存処理

    // sendgridのcontactに登録 $mailerService->saveUser($user); // …ビューの表示処理 } 型を インターフェース に変更
  90. インターフェースのまとめ ▪ (インターフェースを変更しない限りは)処理を変更せず に実装を差し替えることができる ▪ 自動注入と組み合わせることで強力に

  91. まとめ

  92. なぜサービスコンテナは難しいのか サービス DI サービス コンテナ インター フェース 自動注入

  93. サービスのまとめ ▪ 見やすさ – 詳細な処理がコントローラに書かれていないので 見やすい ▪ 再利用性 – サービスのインスタンスを生成すれば

    コピペしなくて済む ▪ 保守性 – 似た処理が同じところに集まったので メンテナンスしやすい
  94. DIのまとめ ▪ DIとは、インスタンスの生成に必要な設定を引数で渡し てあげること ▪ 設定を外から渡すことで、インスタンスの設定を状況に 合わせて指定できる

  95. サービスコンテナのまとめ ▪ DIを使うと、コントローラでnewがいっぱい出てくる ▪ サービスをDIするのをサポートするツール ▪ サービスコンテナはサービスの入ったグローバルな配列 ▪ サービスの生成方法についてはサービスプロバイダに 記述する

  96. Tips: 自動注入 ▪ サービスコンテナに登録するときのキーをクラス名にす ると、ちょっとラクに書けます! ▪ コントローラで引数に取ると、 そのままサービスが生成されます。 ▪ 自動注入されるサービスは、

    引数の型をもとに判断されます。
  97. インターフェースのまとめ ▪ (インターフェースを変更しない限りは)処理を変更せず に実装を差し替えることができる ▪ 自動注入と組み合わせることで強力に

  98. ご清聴 ありがとうございました