WordPress でサムネイル画像の自動生成に Intervention Image 2 を使ってみた

# 概要
PHP の画像処理ライブラリである [Intervention Image](https://image.intervention.io/v2) のバージョン２を WordPress で使う方法について調べてみました。

WordPress で某サイトの画像一覧を作成中なのですが、元画像( 400KB 程度)が 50 ファイルもあると 1ページの転送量が 10MB をゆうに超えてしまうので、元画像から動的にサムネイルを自動生成してくれる、ちょうど Apache/Nginx の small_light のような機能が欲しいなぁ～とググってみました。環境がレンタルサーバですので、当然モジュールの追加などはできず。

Laravel の Intervention Image がよさげでこれを WordPress プラグインで使えないか探してみたところ、以下のプラグインが見つかりました。

https://github.com/getdave/wp-intervention

# プラグインのインストール
ソースを見ても、まんま Laravel ライブラリで WordPress プラグイン用の Wrapper という感じです。
このプラグインをそのまま plugins フォルダに放り込んで有効化したのですが、残念ながらエラーが出て有効化できませんでした。すでに4年ほど前に更新が止まっていますので自力で解決するしかないかと。

![error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237317/672f4df7-2c16-c7c5-001c-d0728bc732f4.png)
エラーログはこんな感じ
~~~php:debug.log
PHP Warning:  require(wp-content\plugins\wp-intervention\vendor\composer/../phpunit/phpunit/src/Framework/Assert/Functions.php): failed to open stream: No such file or directory in wp-content\plugins\wp-intervention\vendor\composer\autoload_real.php on line 66
PHP Fatal error:  require(): Failed opening required 'wp-content\plugins\wp-intervention\vendor\composer/../phpunit/phpunit/src/Framework/Assert/Functions.php' (include_path='.;C:\php\pear') wp-content\plugins\wp-intervention\vendor\composer\autoload_real.php on line 66
~~~


phpunit ライブラリのエラーで本番環境では不要ですので、composer.json から require-dev の項目を除外し composer update します。
~~~json:composer.json
{
    "name": "getdave/wp-intervention",
    "description": "WP Intervention provides on-demand image manipulation for WordPress via the Intervention Library",
    "license": "MIT",
    "authors": [
        {
            "name": "David Smith",
            "email": "getdavemail@gmail.com",
            "homepage": "https://aheadcreative.co.uk"
        }
    ],
    "type": "wordpress-plugin",
    "require": {
        "intervention/image": "^2.5",
        "composer/installers": "*"
    },
    "require-dev": {
        "symfony/var-dumper": "*",
        "php-mock/php-mock-phpunit": "*"
    },
    "autoload": {
        "files": [
            "src/plugin.php",
            "src/intervention-wrapper.php",
            "src/globals.php"
        ]
    }
}
~~~


「--no-dev」を付け composer update を実行します。
~~~php:
> composer update --no-dev
The "composer/installers" plugin was skipped because it requires a Plugin API version ("^1.0") that does not match your Composer installation ("2.3.0"). You may need to run composer update with the "--no-plugins" option.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 7 installs, 32 updates, 7 removals
  - Removing phpdocumentor/reflection-common (2.0.0)
  - Removing phpdocumentor/reflection-docblock (5.1.0)
  - Removing phpdocumentor/type-resolver (1.1.0)
  - Removing phpspec/prophecy (v1.10.3)
  - Removing phpunit/php-token-stream (4.0.0)
  - Removing symfony/polyfill-ctype (v1.14.0)
  - Removing webmozart/assert (1.7.0)
  - Upgrading composer/installers (v1.8.0 => v2.2.0)
  - Upgrading doctrine/instantiator (1.3.0 => 1.5.0)
  - Upgrading guzzlehttp/psr7 (1.6.1 => 2.6.2)
  - Upgrading intervention/image (2.5.1 => 2.7.2)
  - Upgrading myclabs/deep-copy (1.9.5 => 1.11.1)
  - Locking nikic/php-parser (v5.0.0)
  - Upgrading phar-io/manifest (1.0.3 => 2.0.3)
  - Upgrading phar-io/version (2.0.1 => 3.2.1)
  - Upgrading php-mock/php-mock (2.2.1 => 2.4.1)
  - Upgrading php-mock/php-mock-integration (2.1.0 => 2.2.1)
  - Upgrading php-mock/php-mock-phpunit (2.6.0 => 2.9.0)
  - Upgrading phpunit/php-code-coverage (8.0.1 => 9.2.30)
  - Upgrading phpunit/php-file-iterator (3.0.0 => 3.0.6)
  - Upgrading phpunit/php-invoker (3.0.0 => 3.1.1)
  - Upgrading phpunit/php-text-template (2.0.0 => 2.0.4)
  - Upgrading phpunit/php-timer (3.0.0 => 5.0.3)
  - Upgrading phpunit/phpunit (9.0.1 => 9.6.16)
  - Locking psr/http-factory (1.0.2)
  - Upgrading psr/http-message (1.0.1 => 2.0)
  - Locking sebastian/cli-parser (1.0.1)
  - Locking sebastian/code-unit (1.0.8)
  - Upgrading sebastian/code-unit-reverse-lookup (2.0.0 => 2.0.3)
  - Upgrading sebastian/comparator (4.0.0 => 4.0.8)
  - Locking sebastian/complexity (2.0.3)
  - Upgrading sebastian/diff (4.0.0 => 4.0.5)
  - Upgrading sebastian/environment (5.0.1 => 5.1.5)
  - Upgrading sebastian/exporter (4.0.0 => 4.0.5)
  - Upgrading sebastian/global-state (4.0.0 => 5.0.6)
  - Locking sebastian/lines-of-code (1.0.4)
  - Upgrading sebastian/object-enumerator (4.0.0 => 4.0.4)
  - Upgrading sebastian/object-reflector (2.0.0 => 2.0.4)
  - Upgrading sebastian/recursion-context (4.0.0 => 4.0.5)
  - Upgrading sebastian/resource-operations (3.0.0 => 3.0.3)
  - Upgrading sebastian/type (2.0.0 => 3.2.1)
  - Upgrading sebastian/version (3.0.0 => 3.0.2)
  - Upgrading symfony/polyfill-mbstring (v1.14.0 => v1.29.0)
  - Locking symfony/polyfill-php80 (v1.29.0)
  - Upgrading symfony/var-dumper (v5.0.5 => v5.4.35)
  - Upgrading theseer/tokenizer (1.1.3 => 1.2.2)
Writing lock file
Installing dependencies from lock file
Package operations: 3 installs, 3 updates, 2 removals
  - Removing symfony/var-dumper (v5.0.5)
  - Removing symfony/polyfill-mbstring (v1.14.0)
composer/installers contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
Do you trust "composer/installers" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] y
  - Installing composer/installers (v2.2.0): Extracting archive
  - Installing ralouphie/getallheaders (3.0.3): Extracting archive
  - Upgrading psr/http-message (1.0.1 => 2.0): Extracting archive
  - Installing psr/http-factory (1.0.2): Extracting archive
  - Upgrading guzzlehttp/psr7 (1.6.1 => 2.6.2): Extracting archive
  - Upgrading intervention/image (2.5.1 => 2.7.2): Extracting archive
Generating autoload files
3 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found
~~~

これで無事プラグインを「有効化」できました。intervention/image は v2.7.2 に upgrade されてますね。

![true.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237317/3d713d16-f063-ab6b-04c4-90f3a694c844.png)

# 使い方
~~~php:
<?php
    // 1000x1000px
    $src_image_url = 'https://some.url/someapp/wp-content/uploads/2024/02/someimage.jpg'; 
    $intervention_args = array(
        'fit' => array(
            '200', // thumbnail x (px)
            '200'  // thumbnail y (px)
        ),
    );
     $thumbnail_image_url = wp_intervention($src_image_url, $intervention_args, array('quality' => 100));


    echo "<img src='{$thumbnail_image_url}'>";
    // <img src='https://some.url/someapp/wp-content/uploads/intervention/cache/someimage-3446293700.jpg'>
?>
~~~
上記の場合、1000x1000px の someimage.jpg 画像が 200x200px に縮小変換され cache フォルダに格納されます。サムネイル画像を自動生成しそのパスを返す感じです。使う側は変換後のパスを意識せず済むので助かります。

ブラウザの F12 開発者モード > ネットワーク で確認したところ、導入前 20数MB 程度だった転送量が 3MB 程度まで低減することができました。
    
### キャッシュの有効期限を変更する
~~~php:functions.php
function modify_wpi_cache_files_period( $period ) {
	$period = 86400 * 7; // 1 week(604,800sec)
    return $period;
}
add_filter( 'wpi_clean_outdate_cache_files_period', 'modify_wpi_cache_files_period' );
~~~
### キャッシュの保存フォルダを変更する
~~~php:functions.php
function modify_wpi_cache_dir( $dir ) {
    $uploads_info = wp_upload_dir();
    $dir = $uploads_info['basedir'] . '/somefolder/cache/';
    return $dir;
}
add_filter( 'wpi_cache_directory', 'modify_wpi_cache_dir' );
~~~

# 終わりに
案外簡単に実装できました。初回のキャッシュ作成に負荷がかかりますので、定期的にクローラでリクエストを投げまわしておくのもよいかと思います。負荷と言っても若干ロードが遅延する程度でした。