プラグイン間のライブラリの依存関係を回避するには? (php-scoper)

Tech

WordPressプラグインにおけるライブラリの管理

公式プラグインの中にはサードーパーティのライブラリを搭載しているものがあります。
代表的なものとしては、aws-sdkやGuzzleなどがあります。

PHPの一般的な開発であれば、それらのライブラリはcomposerを利用してプロジェクト毎にバンドルして管理する方法が正しいと思われます。

一方で、WordPressのプラグインとテーマはスタンドアロンで機能を提供する必要があるため、ライブラリの管理は各プラグインで行われます。
また、プラグイン利用者の環境は、 composer install ができるものばかりではないですので、公式プラグインは、インストールから利用までに特別な操作を必要とする前提では作られていません。

ライブラリの競合

よって、公式プラグインでライブラリを使いたい場合はインストールしたライブラリを直でコミットする必要がある、ということになります。

実際、有名な公式プラグインでもライブラリを直にコミットしています。
↓この辺とか
WP Mail SMTP by WPForms
C3 Cloudfront Cache Controller

これが結構めんどくさい問題を含んでまして、ライブラリは基本的にバージョンが異なっていても同じ名前空間を使用しているため、複数プラグインで同一のライブラリを管理していたりすると、競合が発生します。
例えば、別々のプラグインAとBがあったとして、両者で異なるバージョンのライブラリを参照していた場合、どちらかのプラグイン(おそらく読み込み順序の遅い方)で予期せぬ挙動が発生する可能性があります。

PHP-Scoper

ライブラリの管理がしんどいということがわかったところで、じゃあどうやって解消すんの?っていう話なんですが、
名前空間の重複がダメなら名前空間にプラグイン固有のprefixをつけてあげればいいじゃない、というアプローチをしているのが、PHP-Scoperというツールになります。

こちらのツールはYoast SEOでも利用されています。
詳細な利用方法はgithubや海外のWordPressエンジニアが解説しているものもあります (DELICIOUS BRAINS)。

競合状態を発生させる

実際にライブラリが競合になる状態を作ってPHP-Scoperと使ってみます。
今回はaws-sdkをV2とV3を使うプラグインをそれぞれ作ってみます。

V2

sdkのV2をインストールしておいてください。

composer.json
{
    "require": {
        "aws/aws-sdk-php": "2.8"
    }
}

プラグイン側では、autoloaderを読み込んでS3Clientを作成します。

aws-v2.php
<?php
/**
 * Plugin Name:     AWS-SDK v2のプラグイン
 * Version:         0.1.0
 */

require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';

use Aws\S3\S3Client;

$s3 = S3Client::factory(
    array(
        'profile' => 'my-credential-profile',
        'region'  => 'us-east-1',
    )
);

V3

sdkのV3をインストールします。

composer.json
{
    "require": {
        "aws/aws-sdk-php": "3.145"
    }
}

こちらもインストールしたライブラリを読み込んでS3Clientを作っておきます。

aws-v3.php
<?php
/**
 * Plugin Name:     AWS-SDK v3のプラグイン
 * Version:         0.1.0
 */

require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';

use Aws\S3\S3Client;

$s3 = new S3Client(
    array(
        'profile' => 'my-credential-profile',
        'region'  => 'us-east-1',
        'version' => '2006-03-01',
    )
);

この状態で2つのプラグインを有効化します。
V3→V2の順で有効化すると。。

V2が有効化出来ません。
これは、名前空間の重複によりV2プラグインがV3のaws-sdkを参照してしまっているために発生したエラーになります。
(V3からS3クライアントの生成の引数にversionが必須になったけど足りない)

競合を解消する

ここからPHP-Scoperを使用していきます。
PHP-Scoper自体はgithubのreadmeを参考にしてインストールしておいてください。

どちらのプラグインでも良いのですが、とりあえずV3のプラグイン側でやってみましょう。
現在のディレクトリ構成は↓のようになってると思います。

.
├── vendor
├── composer.json
├── composer.lock
└── aws-v3.php

プラグインのルートで以下のコマンドを実行してください (結構時間がかかります)。

$ php-scoper add-prefix

新しくbuildディレクトリが作成されていることが確認出来ます。

.
├── build
│   ├── vendor
│   ├── composer.json
│   ├── composer.lock
│   └── aws-v3.php
├── vendor
├── composer.json
├── composer.lock
└── aws-v3.php

build配下には、php-scoperを実行したカレントディレクトリのファイル以下の名前空間にprefixを付けられたものがコピーされています。
試しに vendor/aws/aws-sdk-php/src のいずれかのPHPファイルを見てみると、

namespace _PhpScoperhogehoge\Aws\HOGE;

のように変更されていることが確認出来ます。

名前空間が変更されているので、以下のコマンドでautoloadを更新しておきましょう。

$ composer dump-autoload

あとはV3のプラグインでrequireするautoload.phpをbuild配下のものに変更します。

aws-v3.php
<?php
/**
 * Plugin Name:     AWS-SDK v3のプラグイン
 * Version:         0.1.0
 */

require_once plugin_dir_path( __FILE__ ) . 'build/vendor/autoload.php';

use _PhpScoperhogehoge\Aws\S3\S3Client;

$s3 = new S3Client(
    array(
        'profile' => 'my-credential-profile',
        'region'  => 'us-east-1',
        'version' => '2006-03-01',
    )
);

再度、V3→V2の順でプラグインを有効化してみましょう。

正常に有効化出来ました。

タイトルとURLをコピーしました