フォームからPOSTリクエストで受け取った画像サイズを小さくしてデータ容量を削減したい(Laravel10)

## やること
### 実現したい内容
作成しているサービスが低コストで運用していきたいため、フォームからPOSTで送られてきた画像をController内でサイズ変更してから保存したいと思いました。

### 方法
オープンソースのPHP画像処理および操作ライブラリであるInterventionImageを使用していきます。
Laravel(PHP)で画像処理といえばこれって感じのライブラリのようです。

### 環境
Dockerで作成したローカル環境で実施
- PHP: 8.1.18
- Laravel10
- nginx: 1.24.0
- mysql: 8.0

## やったこと
### InterventionImageをインストール
composerを使ってインストールします。
```
composer require intervention/image
```
インストールすると、composer.jsonとcomposer.lockが更新されます。

サービスプロバイダとエイリアスを登録しておきます。
```config/app.php
'providers' => ServiceProvider::defaultProviders()->merge([
    ...中略
    Intervention\Image\ImageServiceProvider::class,
])->toArray(),

'aliases' => Facade::defaultAliases()->merge([
    'Image' => Intervention\Image\Facades\Image::class,
])->toArray(),
```

InterventionImageのインストールはこれだけで完了です。

### Controllerでサイズ変更する
```ItemPostController.php
use Image;
public function store(Request $request)
{
    $itemPost = new ItemPost();
    ...中略
    if (request('image')) {
        $name = date('Ymd_His') . '_' . request()->file('image')->getClientOriginalName();
        $image = request()->file('image');
        $image = Image::make($image);
        // 圧縮
        $image->resize(
            300,
            null,
            function ($constraint) {
                $constraint->aspectRatio();
                $constraint->upsize();
            }
        )->orientate();
    
        $image->save('storage/images/' . $name, 60);
        $itemPost->image = $name;
    }
    $itemPost->save();
}
```
上記でサイズ変更に成功しました。
内容を説明していきます。
```$name = date('Ymd_His') . '_' . request()->file('image')->getClientOriginalName();```
requestで受け取った画像の名前に日付を追加した文字列をファイル名として$nameに格納しておきます。
画像処理には関係ないので省略しますが、最後のところで$itemPostをDBに保存しています。

```$image = request()->file('image');```
$imageにリクエストで受け取ったファイルを格納します。

```$image = Image::make($image);```
use宣言したImageを利用して、InterventionImageを使用しています。
[make関数](https://image.intervention.io/v2/api/make)でリクエストの画像からイメージインスタンスを作成しています。
```php
$image->resize(
    300,
    null,
    function ($constraint) {
        $constraint->aspectRatio();
        $constraint->upsize();
    }
)->orientate();
```
[resize関数](https://image.intervention.io/v2/api/resize)でサイズを変更しています。
横幅を300pxにリサイズしています。aspectRatioを設定することで縦横比は原本のままにしています。upsizeは画像が拡大されないようにしています。
[orientate関数](https://image.intervention.io/v2/api/orientate)はEXIF画像プロファイル設定「方向」を読み取り、画像を回転して画像を正しく表示するメソッドになります。使用することで勝手に画像が反転する現象を解消しております。後述しますが、orientateを使用する場合はexifサポートを有効にする必要があります。

```$image->save('storage/images/' . $name, 60);```
イメージインスタンスを**storage/images/ファイル名** のパスに品質「60」で保存しています。

上記で、**storage/images/ファイル名** のパスに画像処理後の画像が保存される形になります。

## トラブルシューティング
### エラー1: InterventionImageを使用するにはGDサポートを有効にする必要がある
**GD Library extension not available with this PHP installation.** というエラーが発生しました。
GDがインストールされていませんというエラーになります。GDをインストールすることで改善されました。
GDのインストール: https://www.php.net/manual/ja/image.installation.php

具体的には、Dockerを使っているので、以下をPHPのDockerfileに追加しました。
```dockerfile
RUN apt-get install -y zlib1g-dev \ 
    libzip-dev \
    libjpeg-dev \
    libpng-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev

RUN docker-php-ext-install pdo_mysql zip
RUN docker-php-ext-configure \
    gd --with-freetype --with-jpeg
```
GDがJPEG等のフォーマットを扱えるようにオプションを指定しています。

### エラー2: exifサポートが有効になっていない
**Reading Exif data is not supported by this PHP installation.** というエラーが発生しました。

画像が勝手に回転してしまう現象を改善するために```$image->orientate()```を使用しました。
[orientate](https://image.intervention.io/v2/api/orientate)を利用するために[exif](https://www.php.net/manual/ja/book.exif.php)サポートを有効にする必要がある。

PHPのDockerfileに以下を設定することで改善しました。
```dockerfile
RUN docker-php-ext-configure \
    gd --with-freetype --with-jpeg
# 以下を追加
RUN docker-php-ext-install -j$(nproc) gd exif
```

### (参考)1MB以上の画像がフォームから送信できない場合
**413 Request Entity Too Large** というエラーが出た時
以下3つのデータサイズが設定されているか確認する。
- nginx・設定ファイルのclient_max_body_size（もしあるなら変更、なければ追加）
- php.iniのpost_max_size
- php.iniのupload_max_filesize

自分はnginxのdefault.confに下記を追加して改善した。
```default.conf
client_max_body_size 64m;
```
参考: https://blog.capilano-fw.com/?p=256

## まとめ
初めてInterventionImageを使用したので、ちょこちょこエラーが出てしまいましたが、とても簡単に画像加工できることが分かりました。ドキュメントも関数ごとにまとまっていて見やすく、モザイク処理やテキスト挿入などできることは多いので、他のサービスを作る時に画像処理が必要だった場合は、また利用したいと思いました。