Open18

読者コミュニティ|【実践】LaravelでFat Controllerをリファクタしよう〜DIコンテナ・テストコードまで〜

meijinmeijin

リファクタリングを実践する特性上、環境構築が上手くいかないといったトラブルが頻出なので、何かあればこちらにコメントください!

kohei.comkohei.com

ぺこり。環境構築で手間取っております。
以下の現象です。

docker環境構築の際、bookmark-httpsがすぐ落ちてしまう。
リスタートしているが2-3回ほど繰り返して落ちる。
エラーログを見ていると以下のエラー発見
cannot load certificate "/var/lib/https-portal/localhost/local/chained.crt"

meijinmeijin

コメントありがとうございます!謎ですね。
https-portalというDockerコンテナを使ってるので、READMEとかissue漁ってみますね!

meijinmeijin

ちなみにMacはM1以降ですか?Intelですか?その他何か環境情報あれば教えてください!

meijinmeijin

httpsであることにあまり意味がなかった気もするので、もしDockerに抵抗なければhttpsコンテナを使わずに、ブラウザからのアクセスではhttpを使う方針で設定を変えてみるといいかもしれません。

私の方は一旦現仕様のまま動かないかという方針で調べてみます。

meijinmeijin

情報ありがとうございます。

方法については、試したことが無いのでなんともいえませんが多分そういう方針です!

meijinmeijin

軽く調べただけですが、Dockerにrootlessモードというのがあって、これがON/OFFのどちらかによって、80番ポートや443ポートが使えるか?が異なるようです

https://zenn.dev/capriblue/articles/514ae2f0eeafb3

上記記事にrootlessモードかどうかの確認方法など載っているので、よかったらお試しください!

yuhcanyuhcan

すみません、こちらも同様のエラーで環境構築から進めない状況です、、、。
環境としましては、
intelのmacとm2のmacの両方で試しましたが同様のエラーでした。
またrootlessの確認もしましたが通常のdockerでした。
コンテナのエラーよりdocker-compose.ymlのhttps-portalを以下に変更したところ、コンテナは立ち上がりますが400番が帰ってきて進めない状況です。
volumes:
- ./docker/https-portal/cert:/var/lib/https-portal/localhost/local

何か改善案や代替案などありましたらご教授願いたいです。

meijinmeijin

報告ありがとうございます!
なにせ、僕の環境では問題なく動いているので謎ですね…従って、代案も浮かばない状況です。

一旦dockerのvolume修正は暫定的に正しいとして、400の方を深掘りさせてください。(なぜなら400と言い切れるということはLaravelレイヤーの問題であるため)

・m2の方で起きているか?
・m2の方では、起動コマンドが異なるが教材の通りのオプションで起動しているか?
・400と断定できているということはLaravelのエラー画面が出ていると推察します。どのURLを開きどんなエラー画面が出ていますか?(可能ならスクショ)
・環境構築手順のどこまで進めましたか?
・可能ならいくつかのコンテナからログ出力の内容が欲しいです(docker compose logs コンテナ名)

meijinmeijin

ただ、ちょっとそのvolume修正ってバインドの意味が変わってる気がするので、結果コンテナが立ち上がっても上手くいかない説もありますね。
後ほど自分の環境で、dockerディレクトリ以下のtreeを送るので、突き合わせお願いさせてください!

meijinmeijin

また、https-portal側のissue漁りたいのですが、可能なら当該コンテナのエラーログをもっと広い範囲で貰えると助かります(GitHubのissuesを漁りたい)

meijinmeijin

treeコマンドの結果を掲載します。差異がありましたら教えてください!
※https-portal本体が私が教材執筆した頃と現在でVersion差異がある等の理由で、内部挙動が異なっている可能性を考慮しています

tree -L 4 ./docker/https-portal/
./docker/https-portal/
└── cert
    ├── account.key
    ├── default_server
    │   ├── default_server.crt
    │   └── default_server.key
    ├── dhparam.pem
    ├── dynamic-env
    ├── dynamic-env.stamp
    └── localhost
        └── local
            ├── chained.crt -> /var/lib/https-portal/localhost/local/signed.crt
            ├── domain.csr
            ├── domain.key
            └── signed.crt
山本隆山本隆

4点気になったことがありますので、報告いたします。

なお、私の環境は以下のとおりです。

  • チップ Apple M2
  • macOS 14.6.1
  • Docker Desktop 4.34.0

(1) bookmark-httpsが落ちる
Docker コンテナの立ち上げでbookmark-httpsが落ちます。
原因はchained.crtのパスに間違いあるためです。
解決策は、docker-compose.ymlを以下のように修正します。(参考

-      - ./docker/https-portal/cert:/var/lib/https-portal
+      - ./docker/https-portal/cert:/var/lib/https-portal/localhost

(2) Please provide a valid cache path.エラーが発生する
https://localhost/ にアクセスすると以下のエラーメッセージが表示されます。

Please provide a valid cache path.

以下のコマンドを実行することで、エラーが解消します。

docker compose exec app mkdir -p /opt/laravel-bookmark/storage
docker compose exec app mkdir -p /opt/laravel-bookmark/storage/framework/sessions
docker compose exec app mkdir -p /opt/laravel-bookmark/storage/framework/views
docker compose exec app chown -R www-data:www-data /opt/laravel-bookmark/storage
docker compose exec app chmod -R 775 /opt/laravel-bookmark/storage

(3) php.iniのdate.timezoneの値が${TZ}になっている
php.iniのdate.timezoneの値が${TZ}になっています。

% docker compose exec app cat /usr/local/etc/php/php.ini
(省略)
date.timezone = ${TZ}

私はdocker/php/Dockerfileの以下の行を修正して対応しました。

  COPY php.ini /usr/local/etc/php/php.ini
+ RUN sed -i "s/\${TZ}/Asia\/Tokyo/" /usr/local/etc/php/php.ini

  COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

(4) Chapter 25のタイプミス
Chapter 25 実際に外部アクセスはしない”モック”を作成しようにタイプミスがあります。

- final class MockLinkPreview islmplements LinkPreviewInterface
+ final class MockLinkPreview implements LinkPreviewInterface
-       $useCase = new CreateBookmarkUseCase(j);
+       $useCase = new CreateBookmarkUseCase(new MockLinkPreview());

以上です。

meijinmeijin

ありがとうございます!環境情報やDiffの形式で情報提供いただき大変助かります。

bookmark-httpsの問題は、私の環境では再現しなかったため、修正案もいただきありがたいです。そのまま教材にも反映しました。

今後もなにかありましたらよろしくお願いします。

ゆうへいゆうへい

お世話になります!
共有したいことがあるので、記載させていただきます。

感想

良書だと感じました!
あらかじめ用意されたFat Controllerからリファクタリングを進めることでSlim Controllerになっていき、徐々にソースコードの可読性が向上していきます。
リファクタリングをあまりやったことがない自分にとっては、とても良い学びになりました。
手を動かしながら進められて、説明も丁寧なので、理解しやすかったです。

  • 1 章 「ユースケース」を作って処理を引っ越しさせてみよう
  • 2 章 Fat Controller とはどんな状態か知ろう
  • 3 章 FormRequest、Middleware など Laravel の機能を使いこなそう
  • 4 章 サービスコンテナの働きを理解して使いこなそう
  • 5 章 テストコードを書いて品質維持できる体制を作ろう!

1〜3章は問題なく進められましたが、4〜5章は難しさを感じました。
教材が難しいというよりは、技術的に難易度が高いといった感じでしょうか?
4〜5章は難しかったので2周してみましたが、それでも理解度50〜70%くらいです。
サービスコンテナやサービスプロバイダー、テストコードを自走して書けるレベルになっていないので、他の教材で補おうと考えています。
サービスコンテナやテストコードの入門という位置付けとしては良かったと感じています。

ソースコードで気になる点

17ページ

CreateBookmarkUseCase.phpにて、エラーが発生してしまいました。

Symfony\Component\Debug\Exception\FatalThrowableError
Class 'Cardei\LinkPreview\Client' not found

use文を修正することで、エラーを解消することができました。

  • 修正前:use Cardei\LinkPreview\Client;
  • 修正後:use Dusterio\LinkPreview\Client;

LinkPreviewライブラリメンテナンス終了に伴う修正漏れだと予想しています。
他にもCardeiと記載されている箇所が複数あり、それらを全てDusterioに置換した結果、正常に動作しました。
改めて見直した方が良いかなと思います。

24ページ

「CreateBookmarkUseCase を LinkPreviewInterface に依存させる」のBookmarkControllerのcreateメソッド修正に関して、現状は下記のようになっています。

<?php

namespace App\Http\Controllers\Bookmarks;

+j
// 中略

class BookmarkController extends Controller
{
    // 中略

    public function create(CreateBookmarkRequest $request)
    {
-       $useCase = new CreateBookmarkUseCase();
+       $useCase = new CreateBookmarkUseCase(new LinkPreview());
        $useCase->handle($request->url, $request->category, $request->comment);

        // 暫定的に成功時は一覧ページへ
        return redirect('/bookmarks', 302);
    }
    // 中略
}

次の内容が正しいと思ったのですが、いかがでしょうか?

<?php

namespace App\Http\Controllers\Bookmarks;

+use App\Lib\LinkPreview\LinkPreview;
// 中略

class BookmarkController extends Controller
{
    // 中略

    public function create(CreateBookmarkRequest $request)
    {
-       $useCase = new CreateBookmarkUseCase();
+       $useCase = new CreateBookmarkUseCase(new LinkPreview());
        $useCase->handle($request->url, $request->category, $request->comment);

        // 暫定的に成功時は一覧ページへ
        return redirect('/bookmarks', 302);
    }
    // 中略
}
meijinmeijin

ゆうへいさん、感想と間違いの指摘ありがとうございます!恥ずかしながら何度も書き足したりしているうちに、凡ミスが続いてしまっているようです・・・後ほど修正します!

1〜3章は問題なく進められましたが、4〜5章は難しさを感じました。

そうですよね。感想ありがとうございます。個人的にも教材を読むだけでここを理解できる人は、すでにこれまでの開発経験でなんとなく認知していたのが言語化できていなかった、くらいの練度の方くらいかなと思っています。
ただ、これまで一度でも知ったことのある概念かどうかの違いが、今後実務などで相応の課題に直面したときに重要になるのも確かかと思っていて、教材を書く立場としてなんとしてもDIコンテナやテストコードの頭出しはやっておきたかったので書きました!最後まで履修していただいて嬉しいです。