Docker + Laravel 11 + Intervention Image(v3.10)を海外エンジニアから学ぶ

## はじめに
Intervention Imageのversion3についてYouTubeで海外のエンジニアが使い方の動画を作ってくれていたのでハンズオンで学習しました。
また、まるっきりコードをコピーするのではなく自分なりにアレンジしています。
参考元動画↓
[YouTube: Laravel 11 Image Intervention Tutorial: Resize and Crop Image Like a Boss](https://www.youtube.com/watch?v=f2WNXpEPtKk)

## GDの導入
[公式](https://image.intervention.io/v3)ではImagickを推奨していますが今回はGDライブラリを使います。

`phpinfo()`で「GD」と検索してGD Supportがenabledになっていたら準備OKです。

私はDockerで環境構築しており、GDがなかったので[dockerHub](https://hub.docker.com/_/php)を参考にPHPのdockerfileを修正します。
```dockerfile:docker/PHP/dockerfile
FROM php:8.2-fpm
RUN apt-get update && apt-get install -y \
		libfreetype-dev \
		libjpeg62-turbo-dev \
		libpng-dev \
	&& docker-php-ext-configure gd --with-freetype --with-jpeg \
	&& docker-php-ext-install -j$(nproc) gd
```
```:コンテナをリビルド
docker compose up -d --build
```
GDが無事インストールできたようです。
![スクリーンショット 2025-01-18 22.28.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759506/33382a39-a65a-ce36-17b6-c1263b09715c.png)

## Intervention Imageのインストール
```
composer require intervention/image
```
composer.jsonを確認し`"intervention/image": "^3.10",`が記述されました。

## Laravelの記述
### Routeの作成
```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ImageController;

Route::controller(ImageController::class)->group(function(){
    Route::get('image', 'fileUpload');
    Route::post('image', 'storeImage')->name('image.store');
});
```

### ImageControllerの作成
```
php artisan make:controller ImageController
```
```php:ImageController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request; // HTTPリクエストを扱うクラスをインポート
use Illuminate\Support\Facades\Storage; // ストレージ操作用のクラスをインポート
use Intervention\Image\ImageManager; // 画像操作ライブラリ（Intervention Image）のマネージャをインポート
use Intervention\Image\Drivers\Gd\Driver; // GDドライバを使用するためのクラスをインポート

class ImageController extends Controller
{
    // ファイルアップロード画面を表示するメソッド
    public function fileUpload()
    {
        // ビュー 'image-upload.blade.php' を返して表示
        return view('image-upload');
    }

    // アップロードされた画像を処理するメソッド
    public function storeImage(Request $request)
    {
        // 画像ファイルのバリデーションを行う
        $request->validate([
            // 必須、画像形式、許容する拡張子、最大サイズ
            'image' => ['required', 'image', 'mimes:jpg,jpeg,png,svg,gif', 'max:2048'],
        ]);

        // アップロードされた画像ファイルを取得
        $image = $request->file('image');

        // ランダムなユニークな名前を生成（衝突を防ぐために乱数を使用）
        $imageName = uniqid(rand() . '_') . '.' . $image->extension();
        
        // 画像をストレージ（'public/images'ディレクトリ）に保存
        Storage::disk('public')->putFileAs('images', $image, $imageName);

        // 保存した画像ファイルの絶対パスを取得
        $storedPath = Storage::disk('public')->path('images/' . $imageName);

        // 画像操作のためのImageManagerをGDドライバで初期化
        $imgManager = new ImageManager(new Driver());

        // 保存した画像を読み込んでIntervention Imageオブジェクトを作成
        $resizedImage = $imgManager->read($storedPath);

        // 画像のサイズを640x360ピクセルにリサイズ
        $resizedImage->resize(640, 360);

        // リサイズ後の画像を同じパスに上書き保存
        $response = $resizedImage->save($storedPath);

        // 処理成功時のリダイレクト処理
        if ($response) {
            return redirect()->back()->with('success', '画像のアップロードに成功しました。'); // 成功メッセージを添えてリダイレクト
        }

        // 処理失敗時のリダイレクト処理
        return redirect()->back()->with('error', '画像のアップロードに失敗しました。'); // 失敗メッセージを添えてリダイレクト
    }
}

```

### Viewの作成
```php:resource/views/image-upload.blade.php
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>InterventionImage demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
  </head>
  <body>
    <div class="container py-4">
      <div class="row">
        <div class="col-xl-6 m-auto">

          @if (Session::has('success'))
              <div class="alert alert-success">
                {{ Session::get('success') }}
              </div>

          @elseif(Session::has('error'))
              <div class="alert alert-danger">
                {{ Session::get('error') }}
              </div>
          @endif

          <form action="{{ route('image.store') }}" method="POST" enctype="multipart/form-data">
            @csrf
            <div class="card">
              <div class="card-header">
                <h4 class="card-title">Image Intervention in Laravel</h4>
              </div>
              <div class="card-body">
                <div class="form-group">
                  <label for="image">画像</label><br>
                  <input type="file" name="image" id="image">
                  <x-input-error for="image" class="mt-2 text-danger" />
                </div>
              </div>
              <div class="card-footer">
                <button type="submit" class="btn btn-primary">アップロード</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
  </body>
</html>

```
bootstrapを使っているようです。
![スクリーンショット 2025-01-19 0.01.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759506/6a065b81-0519-2ecd-43ea-6be16ae6e837.png)

## 動作確認
![beach.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759506/bad746ae-1945-e176-568f-8d664a643a4e.jpeg)
![スクリーンショット 2025-01-19 16.56.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759506/e0f2e90f-222a-4b98-adf3-551beab884b8.png)
元は1920×1075でしたが、アップロード後
![スクリーンショット 2025-01-19 16.58.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759506/b9629da8-6e93-98d9-1e95-5139969ffc04.png)
640×360リサイズされstorageに保存されたようです。
## 最後に
Intervention Imageバージョン２からproviderの記述がなくなり、手軽に使えた印象です。
あとはStorageに保存するのに苦戦しました。
海外のエンジニアから学び、英語は理解できませんがコードを書くことで理解できます。
何かの助けになれば幸いです

## 公式URL
https://image.intervention.io/v3