画像の動的resize on Laravel

```html
<img src="/path/to/img.jpg" >
```
って書いてて、あなんかこの画像やっぱ重いなぁと思ったら

```html
<img src="{{ resize('/path/to/img.jpg' ,200, 200) }}" >
<!-- /storage/cache/aerwsvv4w4cey75hf.jpg のように置換されます-->
```

って書き換えるだけで

- サムネイル生成（初回アクセス時のみ）
- ２回目以後のアクセスは、キャッシュ画像に直接アクセス
- サムネイルは、画像の更新や指定サイズの変更タイミングで自動更新

される実装です。

```bash:command
$ composer require intervention/image
$ php artisan storage:link
$ mkdir storage/app/public/cache
```

```php:DynamicImage.php
<?php

namespace App\Services;

use Intervention\Image\Image;

/**
 * Class DynamicThumb
 * @package App\Services
 */
class DynamicImage
{
    const CACHE_DIR = 'cache';
    const IMG_QUALITY = 80;

    /**
     * resize処理
     * @param string $imagePath
     * @param array $query
     * @return string|null
     */
    public static function resize(string $imagePath, array $query): string
    {
        return self::execute($imagePath, __METHOD__, $query);
    }

    /**
     * crop処理
     * @param string $imagePath
     * @param array $query
     * @return string|null
     */
    public static function crop(string $imagePath, array $query): string
    {
        return self::execute($imagePath, __METHOD__, $query);
    }

    /**
     * fit処理
     * @param string $imagePath
     * @param array $query
     * @return string|null
     */
    public static function fit(string $imagePath, array $query): string
    {
        return self::execute($imagePath, __METHOD__, $query);
    }

    /**
     * キャッシュがなければ生成して publicパスを返す
     * @param string $imagePath
     * @param string $action
     * @param array $query
     * @return string|null
     */
    private static function execute(string $imagePath, string $action, array $query): ?string
    {
        if (!self::cacheExists($imagePath, $query)) {

            $image = \Image::make(public_path($imagePath));

            $action = $action . 'Image';
            $image = $action($image, $query);

            $image->save(self::getFullPathOfCacheImage($imagePath, $query), self::IMG_QUALITY);
        }

        return self::getPublicPathOfCacheImage($imagePath, $query);
    }

    /**
     * キャッシュファイル存在するか？
     * @param string $imagePath
     * @param array $query
     * @return bool
     */
    private static function cacheExists(string $imagePath, array $query): bool
    {
        return file_exists(self::getFullPathOfCacheImage($imagePath, $query));
    }

    /**
     * キャッシュディレクトリのpublicパス
     * @return string
     */
    private static function getPublicPathOfCacheDir(): string
    {
        return '/storage/' . self::CACHE_DIR . '/';
    }

    /**
     * キャッシュディレクトリのフルパス
     * @return string
     */
    private static function getFullPathOfCacheDir(): string
    {
        return storage_path('app/public/' . self::CACHE_DIR) . '/';
    }

    /**
     * キャッシュ画像のbaseName
     * @param string $imagePath
     * @param array $query
     * @return string
     */
    private static function getBaseNameOfCacheImage(string $imagePath, array $query): string
    {
        return md5(http_build_query([
            'path' => $imagePath,
            'query' => $query,
            'modified' => filemtime(public_path($imagePath))
        ])) . '.jpg';
    }

    /**
     * キャッシュ画像のpublicパス
     * @param string $imagePath
     * @param array $query
     * @return string
     */
    private static function getPublicPathOfCacheImage(string $imagePath, array $query): string
    {
        return self::getPublicPathOfCacheDir() . self::getBaseNameOfCacheImage($imagePath, $query);
    }

    /**
     * キャッシュ画像のフルパス
     * @param string $imagePath
     * @param array $query
     * @return string
     */
    private static function getFullPathOfCacheImage(string $imagePath, array $query): string
    {
        return self::getFullPathOfCacheDir() . self::getBaseNameOfCacheImage($imagePath, $query);
    }

    /**
     * $width x $height に収まるよう縮小
     * @param Image $image
     * @param array $query
     * @return Image
     */
    private static function resizeImage(Image $image, array $query): Image
    {
        $width = $height = null;
        extract($query);

        if ($image->width() < $image->height()) {
            $image->resize($width, null, function ($constraint) {
                $constraint->aspectRatio();
                $constraint->upsize();
            });
        } else {
            $image->resize(null, $height, function ($constraint) {
                $constraint->aspectRatio();
                $constraint->upsize();
            });
        }

        return $image;
    }

    /**
     * $position(top, center ,bottom)を起点に、$width x $height に収まるようトリミング
     * @param Image $image
     * @param array $query
     * @return Image
     */
    private static function fitImage(Image $image, array $query): Image
    {
        $width = $height = $position = null;
        extract($query);

        $image->fit($width, $height, function ($constraint) {
            $constraint->upsize();
        }, $position ?? 'center');

        return $image;
    }

    /**
     * $x x $yを起点に $width x $height で切り出す
     * @param Image $image
     * @param array $query
     * @return Image
     */
    private static function cropImage(Image $image, array $query): Image
    {
        $width = $height = $x = $y = null;
        extract($query);

        if ($x) {
            $image->crop($width, $height, $x, $y);
        } else {
            $image->crop($width, $height);
        }

        return $image;
    }
}
```

```php:helpers.php
<?php

use App\Services\DynamicImage;


if(!function_exists('resize')){

    function resize(string $basePath, int $width, int $height): string
    {
        return DynamicImage::resize($basePath, compact('width', 'height'));
    }
}

if(!function_exists('crop')){

    function crop(string $basePath, int $width, int $height, ?int $x = null, ?int $y = null): string
    {
        return DynamicImage::crop($basePath, compact('width', 'height', 'x', 'y'));
    }
}

if(!function_exists('fit')){

    function fit(string $basePath, int $width, int $height, ?string $position = null): string
    {
        return DynamicImage::fit($basePath, compact('width', 'height', 'position'));
    }
}



```