WooCommerceやGravity Formsのような人気プラグインからもわかるように、WordPressプラグインは機能を追加して拡張することができます。別のブログ記事『拡張機能対応のWordPressプラグインの設計方法』でご紹介していますが、WordPressプラグインを拡張可能にするには、以下2つの方法があります。

  1. 拡張プラグインで他の機能を組み込めるようにフック(アクションとフィルター)を設定する
  2. 拡張プラグインが継承できるPHPクラスを用意する

1つ目の方法では、利用できるフックとその使用方法を説明したドキュメントが必要になります。対照的に2つ目の方法では、すぐに使えるコードを拡張モジュールで用意することで、包括的なドキュメントを作る手間が省けます。コードとともにドキュメントを作成すると、プラグインの管理やリリースが複雑になる恐れがあります。

機能の実装方法をドキュメントで説明するのではなく、PHPクラスを直接コードで提供することで、開発者によるその後の作業が簡単になります。

今回は、WordPressプラグインを中心とした統合型エコシステムの形成を目標として、知っておきたいテクニックをいくつかご紹介します。

WordPressプラグインでベースとなるPHPクラスを定義する

WordPressプラグインには、拡張機能対応のプラグインを想定したPHPクラスが含まれるものです。このPHPクラスは、メインプラグインでは使用せずとも他のプラグインを考慮して用意されます。

例として、オープンソースのGato GraphQLプラグインでどのように実装されているかを見てみます。

AbstractPluginクラス

AbstractPluginは、メインのGato GraphQLプラグインとその拡張機能の両方を表します。

abstract class AbstractPlugin implements PluginInterface
{
  protected string $pluginBaseName;
  protected string $pluginSlug;
  protected string $pluginName;

  public function __construct(
    protected string $pluginFile,
    protected string $pluginVersion,
    ?string $pluginName,
  ) {
    $this->pluginBaseName = plugin_basename($pluginFile);
    $this->pluginSlug = dirname($this->pluginBaseName);
    $this->pluginName = $pluginName ?? $this->pluginBaseName;
  }

  public function getPluginName(): string
  {
    return $this->pluginName;
  }

  public function getPluginBaseName(): string
  {
    return $this->pluginBaseName;
  }

  public function getPluginSlug(): string
  {
    return $this->pluginSlug;
  }

  public function getPluginFile(): string
  {
    return $this->pluginFile;
  }

  public function getPluginVersion(): string
  {
    return $this->pluginVersion;
  }

  public function getPluginDir(): string
  {
    return dirname($this->pluginFile);
  }

  public function getPluginURL(): string
  {
    return plugin_dir_url($this->pluginFile);
  }

  // ...
}

AbstractMainPluginクラス

AbstractMainPluginAbstractPluginを継承しメインプラグインを担います。

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function __construct(
    string $pluginFile,
    string $pluginVersion,
    ?string $pluginName,
    protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,
  ) {
    parent::__construct(
      $pluginFile,
      $pluginVersion,
      $pluginName,
    );
  }

  // ...
}

AbstractExtensionクラス

同様に、AbstractExtensionAbstractPluginを継承し拡張機能を扱います。

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
  public function __construct(
    string $pluginFile,
    string $pluginVersion,
    ?string $pluginName,
    protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,
  ) {
    parent::__construct(
      $pluginFile,
      $pluginVersion,
      $pluginName,
    );
  }

  // ...
}

なお、AbstractExtensionはメインプラグインに含まれ、拡張機能を登録・初期化する機能を担います。これはメインプラグイン自体ではなく拡張モジュールにより使用されます。

AbstractPluginクラスには、さまざまなタイミングで呼び出される共有の初期化コードが含まれます。これらのメソッドは祖先のレベルで定義されますが、継承したクラスに応じてそれぞれのライフサイクルに従って呼び出される仕様です。

メインプラグインと拡張機能は、対応するクラスのsetupメソッドを実行することで初期化されます。

たとえば、Gato GraphQLではgatographql.phpで行われます。

$pluginFile = __FILE__;
$pluginVersion = '2.4.0';
$pluginName = __('Gato GraphQL', 'gatographql');
PluginApp::getMainPluginManager()->register(new Plugin(
  $pluginFile,
  $pluginVersion,
  $pluginName
))->setup();

setupメソッド

setupは、祖先レベルに位置し、ここには、プラグインが無効になった際に登録を解除するなど、プラグインとその拡張機能間の共通ロジックが記述されます。このメソッドにはfinalを付与せず、継承するクラスが機能を追加するためにオーバーライド可能にします。

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function setup(): void
  {
    register_deactivation_hook(
      $this->getPluginFile(),
      $this->deactivate(...)
    );
  }

  public function deactivate(): void
  {
    $this->removePluginVersion();
  }

  private function removePluginVersion(): void
  {
    $pluginVersions = get_option('gatographql-plugin-versions', []);
    unset($pluginVersions[$this->pluginBaseName]);
    update_option('gatographql-plugin-versions', $pluginVersions);
  }
}

メインプラグインのsetupメソッド

メインプラグインのsetupメソッドは、アプリケーションのライフサイクルを初期化します。initializeconfigureComponentsconfigurebootなどのメソッドを通してメインプラグインの機能を実行し、拡張機能に対応するアクションフックをトリガーします。

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function setup(): void
  {
    parent::setup();

    add_action('plugins_loaded', function (): void
    {
      // 1. メインプラグインの初期化
      $this->initialize();

      // 2. 拡張機能の初期化
      do_action('gatographql:initializeExtension');

      // 3. 主なプラグインコンポーネントの設定
      $this->configureComponents();

      // 4. 拡張コンポーネントの設定
      do_action('gatographql:configureExtensionComponents');

      // 5. メインプラグインの設定
      $this->configure();

      // 6. 拡張機能の設定
      do_action('gatographql:configureExtension');

      // 7. メインプラグインの起動
      $this->boot();

      // 8. 拡張機能の起動
      do_action('gatographql:bootExtension');
    }

    // ...
  }
  
  // ...
}

拡張機能のsetupメソッド

AbstractExtensionクラスは対応するフックでロジックを実行します。

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
  // ...

  final public function setup(): void
  {
    parent::setup();

    add_action('plugins_loaded', function (): void
    {
      // 2. 拡張機能の初期化
      add_action(
        'gatographql:initializeExtension',
        $this->initialize(...)
      );

      // 4. 拡張コンポーネントの設定
      add_action(
        'gatographql:configureExtensionComponents',
        $this->configureComponents(...)
      );

      // 6. 拡張機能の設定
      add_action(
        'gatographql:configureExtension',
        $this->configure(...)
      );

      // 8. 拡張機能の起動
      add_action(
        'gatographql:bootExtension',
        $this->boot(...)
      );
    }, 20);
  }
}

initializeconfigureComponentsconfigurebootはメインプラグインと拡張機能の両方に共通し、ロジックを共有することができます。この共有ロジックはAbstractPluginクラスに格納されます。

たとえば、configureメソッドはプラグインまたは拡張機能を設定し、callPluginInitializationConfigurationを呼び出します。これは、メインプラグインと拡張機能で実装が異なり、抽象クラスとして定義されています。getModuleClassConfigurationはデフォルトの動作を管理しますが、必要に応じてオーバーライド可能です。

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function configure(): void
  {
    $this->callPluginInitializationConfiguration();

    $appLoader = App::getAppLoader();
    $appLoader->addModuleClassConfiguration($this->getModuleClassConfiguration());
  }

  abstract protected function callPluginInitializationConfiguration(): void;

  /**
   * @return array<class-string<ModuleInterface>,mixed> [key]: Module class, [value]: Configuration
   */
  public function getModuleClassConfiguration(): array
  {
    return [];
  }
}

メインプラグインは、callPluginInitializationConfigurationの実装を行います。

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  // ...

  protected function callPluginInitializationConfiguration(): void
  {
    $this->pluginInitializationConfiguration->initialize();
  }
}

拡張クラスの実装は以下のとおりです。

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
  // ...

  protected function callPluginInitializationConfiguration(): void
  {
    $this->extensionInitializationConfiguration?->initialize();
  }
}

initializeconfigureComponentsbootメソッドは祖先レベルで定義され、継承したクラスでオーバーライドすることができます。

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function initialize(): void
  {
    $moduleClasses = $this->getModuleClassesToInitialize();
    App::getAppLoader()->addModuleClassesToInitialize($moduleClasses);
  }

  /**
   * @return array<class-string<ModuleInterface>> List of `Module` class to initialize
   */
  abstract protected function getModuleClassesToInitialize(): array;

  public function configureComponents(): void
  {
    $classNamespace = ClassHelpers::getClassPSR4Namespace(get_called_class());
    $moduleClass = $classNamespace . '\\Module';
    App::getModule($moduleClass)->setPluginFolder(dirname($this->pluginFile));
  }

  public function boot(): void
  {
    // デフォルトでは何もしない
  }
}

メソッドはすべてAbstractMainPluginまたはAbstractExtensionによってオーバーライドすることができます。

メインプラグインの場合、setupはプラグインまたはその拡張機能が有効または無効になった際に、WordPressのインスタンスからキャッシュをクリアします。

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function setup(): void
  {
    parent::setup();

    // ...

    // メインプラグイン固有のメソッド
    add_action(
      'activate_plugin',
      function (string $pluginFile): void {
        $this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
      }
    );
    add_action(
      'deactivate_plugin',
      function (string $pluginFile): void {
        $this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
      }
    );
  }

  public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void
  {
    // 簡素化のためコードを削除
  }

  // ...
}

deactivateメソッドもキャッシュをクリアし、bootメソッドはメインプラグインに対してのみ追加のアクションフックを実行します。

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function deactivate(): void
  {
    parent::deactivate();

    $this->removeTimestamps();
  }

  protected function removeTimestamps(): void
  {
    $userSettingsManager = UserSettingsManagerFacade::getInstance();
    $userSettingsManager->removeTimestamps();
  }

  public function boot(): void
  {
    parent::boot();

    add_filter(
      'admin_body_class',
      function (string $classes): string {
        $extensions = PluginApp::getExtensionManager()->getExtensions();
        $commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();
        foreach ($extensions as $extension) {
          $extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension->getPluginSlug()] ?? null;
          if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {
            continue;
          }
          return $classes . ' is-gatographql-customer';
        }
        return $classes;
      }
    );
  }
}

上で紹介したすべてのコードから、WordPressプラグインを設計・コーディングする際には、拡張機能の要件を考慮してできる限り拡張機能間でコードを再利用する必要があるのは明らかです。これには、健全なオブジェクト指向プログラミングのパターン(SOLID原則など)を実装するのが有用です。これにより、コードベースを長期にわたって保守管理することができます。

バージョン依存関係の宣言と検証

拡張モジュールはプラグインが提供するPHPクラスを継承するため、必要なバージョンのプラグインが存在するかどうかを確認することが重要です。これを怠ると、サイトがダウンする原因となる衝突が発生する可能性があります。

たとえば、AbstractExtensionクラスが更新され、以前の3.4.0からメジャーバージョンの4.0.0がリリースされた場合、バージョンを確認せずに拡張機能を読み込むとPHPエラーが発生し、WordPressを読み込めなくなるかもしれません。

これを回避するため、拡張機能でインストールされているプラグインのバージョンが3.x.xであることを検証しなければなりません。バージョン4.0.0がインストールされると拡張機能は無効になり、エラーを防ぐことができます。

拡張機能では、以下のロジックを使用してこの検証を実行できます。このロジックは拡張機能のメインプラグインファイルplugins_loadedフック(メインプラグインがその時点で読み込まれているため)に基づいて実行されます。拡張機能の管理用にメインプラグインに含まれるExtensionManagerクラスにアクセスします。

/**
 * 拡張機能の作成とセットアップ
 */
add_action(
  'plugins_loaded',
  function (): void {
    /**
     * 拡張機能の名前とバージョン
     *
     * Composerがサポートする接尾辞 -stableを使用
     */
    $extensionVersion = '1.1.0';
    $extensionName = __('Gato GraphQL - Extension Template');

    /**
     * 拡張機能を有効にするために、Gato GraphQLプラグインに
     * 必要な最小バージョン
     */
    $gatoGraphQLPluginVersionConstraint = '^1.0';
    
    /**
     * Gato GraphQLが有効であることを確認
     */
    if (!class_exists(\GatoGraphQL\GatoGraphQL\Plugin::class)) {
      add_action('admin_notices', function () use ($extensionName) {
        printf(
          '<div class="notice notice-error"><p>%s</p></div>',
          sprintf(
            __('Plugin <strong>%s</strong> is not installed or activated. Without it, plugin <strong>%s</strong> will not be loaded.'),
            __('Gato GraphQL'),
            $extensionName
          )
        );
      });
      return;
    }

    $extensionManager = \GatoGraphQL\GatoGraphQL\PluginApp::getExtensionManager();
    if (!$extensionManager->assertIsValid(
      GatoGraphQLExtension::class,
      $extensionVersion,
      $extensionName,
      $gatoGraphQLPluginVersionConstraint
    )) {
      return;
    }
    
    // Composerのオートローダーを読み込む
    require_once(__DIR__ . '/vendor/autoload.php');

    // 拡張インスタンスの作成とセットアップ
    $extensionManager->register(new GatoGraphQLExtension(
      __FILE__,
      $extensionVersion,
      $extensionName,
    ))->setup();
  }
);

上のコードでは、拡張機能がメインプラグインのバージョン制約^1.0Composerのバージョン制約を使用)への依存を宣言しているのがわかります。このため、Gato GraphQLのバージョン2.0.0がインストールされても拡張機能は有効になりません。

バージョン制約は、Semver::satisfiescomposer/semverパッケージにより提供)を呼び出すExtensionManager::assertIsValidメソッドによって検証されます。

use Composer\Semver\Semver;

class ExtensionManager extends AbstractPluginManager
{
  /**
   * 必要なバージョンのGato GraphQL for WPプラグインがインストールされていることを確認
   *
   * アサーションが失敗すると、WordPress管理画面にエラーを表示してfalseを返す
   *
   * @param string|null $mainPluginVersionConstraint プラグインに必要なサーバーのバージョン制約(例:"^1.0" は >=1.0.0 および <2.0.0 を意味する)
   */
  public function assertIsValid(
    string $extensionClass,
    string $extensionVersion,
    ?string $extensionName = null,
    ?string $mainPluginVersionConstraint = null,
  ): bool {
    $mainPlugin = \GatoGraphQL\GatoGraphQL\PluginApp::getMainPluginManager()->getPlugin();
    $mainPluginVersion = $mainPlugin->getPluginVersion();
    if (
      $mainPluginVersionConstraint !== null && !Semver::satisfies(
        $mainPluginVersion,
        $mainPluginVersionConstraint
      )
    ) {
      $this->printAdminNoticeErrorMessage(
        sprintf(
          __('拡張機能またはバンドル<strong>%s</strong>はバージョン制約<strong>%s</strong>を満たすためにプラグイン<code>%s</code>を必要としますが、現在のバージョン<code>%s</code>にはありません。拡張機能またはバンドルが読み込まれていません。', 'gatographql'),
', 'gatographql'),
          $extensionName ?? $extensionClass,
          $mainPlugin->getPluginName(),
          $mainPluginVersionConstraint,
          $mainPlugin->getPluginVersion(),
        )
      );
      return false;
    }

    return true;
  }

  protected function printAdminNoticeErrorMessage(string $errorMessage): void
  {
    \add_action('admin_notices', function () use ($errorMessage): void {
      $adminNotice_safe = sprintf(
        '<div class="notice notice-error"><p>%s</p></div>',
        $errorMessage
      );
      echo $adminNotice_safe;
    });
  }
}

WordPressサーバーで統合テストを実行する

サードパーティの開発者によるプラグインの拡張機能を考慮し、継続的インテグレーションと継続的デリバリー(CI/CD)のためのワークフローを含む、開発およびテスト用のツールを作成することができます。

開発中は、誰でも簡単にDevKinstaを使用してウェブサーバーを立ち上げ、拡張機能をコーディングするプラグインをインストールし、拡張機能がプラグインと互換性があることを検証することができます。

CI/CDにおけるテストを自動化するには、CI/CDサービスからネットワーク経由でウェブサーバーにアクセスできるようにします。これには、テスト・開発用のWordPress環境を簡単に作成できるInstaWPのようなサービスが便利です。

拡張機能のコードベースをGitHubでホストしている場合は、GitHub Actionsを使用してInstaWPに対して統合テストを実行できます。以下のワークフローでは、InstaWPのテスト環境に拡張機能をインストールし(メインプラグインの最新の安定版と一緒に)、統合テストを実行するものです。

name: Integration tests (InstaWP)
on:
  workflow_run:
    workflows: [Generate plugins]
    types:
      - completed

jobs:
  provide_data:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    name: InstaWPにインストールするGitHub Actionsの成果物URLを取得
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: 8.1
          coverage: none
        env:
          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: "ramsey/composer-install@v2"

      - name: GitHubワークフローから成果物URLを取得
        uses: actions/github-script@v6
        id: artifact-url
        with:
          script: |
            const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
              owner: context.repo.owner,
              repo: context.repo.repo,
              run_id: context.payload.workflow_run.id,
            });
            const artifactURLs = allArtifacts.data.artifacts.map((artifact) => {
              return artifact.url.replace('https://api.github.com/repos', 'https://nightly.link') + '.zip'
            }).concat([
              "https://downloads.wordpress.org/plugin/gatographql.latest-stable.zip"
            ]);
            return artifactURLs.join(',');
          result-encoding: string

      - name: InstaWPのArtifacts URL
        run: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}"
        shell: bash

    outputs:
      artifact_url: ${{ steps.artifact-url.outputs.result }}

  process:
    needs: provide_data
    name: InstaWPサイトをテンプレート「integration-tests」から起動し、それに対して統合テストを実行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: 8.1
          coverage: none
        env:
          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: "ramsey/composer-install@v2"

      - name: InstaWPインスタンスを作成
        uses: instawp/wordpress-testing-automation@main
        id: create-instawp
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
          INSTAWP_TEMPLATE_SLUG: "integration-tests"
          REPO_ID: 25
          INSTAWP_ACTION: create-site-template
          ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}

      - name: InstaWPインスタンスURL
        run: echo "InstaWPインスタンスURL - ${{ steps.create-instawp.outputs.instawp_url }}"
        shell: bash

      - name: InstaWPドメインの抽出
        id: extract-instawp-domain        
        run: |
          instawp_domain="$(echo "${{ steps.create-instawp.outputs.instawp_url }}" | sed -e s#https://##)"
          echo "instawp-domain=$(echo $instawp_domain)" >> $GITHUB_OUTPUT

      - name: テストの実行
        run: |
          INTEGRATION_TESTS_WEBSERVER_DOMAIN=${{ steps.extract-instawp-domain.outputs.instawp-domain }} \
          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_USERNAME=${{ steps.create-instawp.outputs.iwp_wp_username }} \
          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_PASSWORD=${{ steps.create-instawp.outputs.iwp_wp_password }} \
          vendor/bin/phpunit --filter=Integration

      - name: InstaWPインスタンスを破棄
        uses: instawp/wordpress-testing-automation@main
        id: destroy-instawp
        if: ${{ always() }}
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
          INSTAWP_TEMPLATE_SLUG: "integration-tests"
          REPO_ID: 25
          INSTAWP_ACTION: destroy-site

このワークフローでは、ログインなしでGitHubから成果物に接続できるようNightly Linkを介して.zipファイルにアクセスし、InstaWPの設定を簡素化しています。

拡張プラグインのリリース

拡張機能のリリースを支援するツールを用意して、可能な限り手順を自動化することができます。

Monorepo Builderは、WordPressプラグインを含むあらゆるPHPプロジェクトを管理できるライブラリです。プロジェクトのバージョンリリースに便利なmonorepo-builder releaseコマンドを用いることで、セマンティックバージョニングに従ってバージョンのメジャー、マイナー、パッチのいずれかのコンポーネントをインクリメント(数値を1増やすこと)することができます。

このコマンドは、特定のロジックを実行するPHPクラスである一連のrelease workersを実行します。デフォルトのワーカーには、新たなバージョンのgit tagを作成するものと、リモートリポジトリにタグをプッシュするものがあります。カスタムワーカーをこれらのステップ前後、あるいは間に組み込むことが可能です。

release workersは設定ファイルで設定します。

use Symplify\MonorepoBuilder\Config\MBConfig;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\AddTagToChangelogReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushNextDevReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushTagReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetCurrentMutualDependenciesReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateReplaceReleaseWorker;

return static function (MBConfig $mbConfig): void {
  // release workers - 実行するための処理
  $mbConfig->workers([
    UpdateReplaceReleaseWorker::class,
    SetCurrentMutualDependenciesReleaseWorker::class,
    AddTagToChangelogReleaseWorker::class,
    TagVersionReleaseWorker::class,
    PushTagReleaseWorker::class,
    SetNextMutualDependenciesReleaseWorker::class,
    UpdateBranchAliasReleaseWorker::class,
    PushNextDevReleaseWorker::class,
  ]);
};

WordPressプラグインの要件に応じてリリースプロセスを拡張するカスタムワーカーを用意することもできます。たとえば、 InjectStableTagVersionInPluginReadmeFileReleaseWorkerは新しいバージョンを拡張機能のreadme.txtファイルの「Stable tag」エントリに設定します。

use Nette\Utils\Strings;
use PharIo\Version\Version;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;

class InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface
{
  public function __construct(
    // このクラスはMonorepo Builderによって提供
    private SmartFileSystem $smartFileSystem,
  ) {
  }

  public function getDescription(Version $version): string
  {
    return 'plugin\のreadme.txtファイルで「Stable tag」が新バージョンを示すようにする';
  }

  public function work(Version $version): void
  {
    $replacements = [
      '/Stable tag:\s+[a-z0-9.-]+/' => 'Stable tag: ' . $version->getVersionString(),
    ];
    $this->replaceContentInFiles(['/readme.txt'], $replacements);
  }

  /**
   * @param string[] $files
   * @param array<string,string> $regexPatternReplacements 検索する正規表現パターンとその置き換え
   */
  protected function replaceContentInFiles(array $files, array $regexPatternReplacements): void
  {
    foreach ($files as $file) {
      $fileContent = $this->smartFileSystem->readFile($file);
      foreach ($regexPatternReplacements as $regexPattern => $replacement) {
        $fileContent = Strings::replace($fileContent, $regexPattern, $replacement);
      }
      $this->smartFileSystem->dumpFile($file, $fileContent);
    }
  }
}

InjectStableTagVersionInPluginReadmeFileReleaseWorkerを設定リストに追加することで、monorepo-builder releaseコマンドを実行してプラグインの新バージョンをリリースするたびに、拡張機能のreadme.txtファイル内の「Stable tag」が自動で更新されます。

拡張機能対応のプラグインをWordPress.orgディレクトリに公開する

拡張機能をWordPressプラグインディレクトリに公開するためのワークフローを配布することも可能です。リモートリポジトリのプロジェクトにタグ付けする場合、以下のワークフローでWordPress拡張プラグインをディレクトリに公開します。

# See: https://github.com/10up/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tag
name: WordPress.orgのプラグインディレクトリ(SVN)にデプロイ
on:
  push:
  tags:
  - "*"

jobs:
  tag:
  name: 新規タグ
  runs-on: ubuntu-latest
  steps:
  - uses: actions/checkout@master
  - name: WordPress Plugin Deploy
    uses: 10up/action-wordpress-plugin-deploy@stable
    env:
    SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
    SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
    SLUG: ${{ secrets.SLUG }}

このワークフローでは、10up/action-wordpress-plugin-deployアクションを使用します。Gitリポジトリからコードを取得し、WordPress.org SVN リポジトリにプッシュするため操作が簡単です。

まとめ

拡張可能なWordPressプラグインを開発する際は、サードパーティの開発者が簡単に拡張できるようにすることが重要です。

広範なドキュメントを提供して拡張方法を提示することもできますが、必要なPHPコード、拡張機能の開発、テスト、リリースに必要なツールを用意する方が効率的です。

拡張機能に必要なコードをプラグインに組み込むことで、開発プロセスを簡素化することができます。

拡張機能対応のWordPressプラグインを構築する予定はありますか?以下のコメント欄でお聞かせください。

Leonardo Losoviz

PHP、WordPress、GraphQLを中心に革新的なウェブ開発のトレンドについて執筆している。仕事情報は、leoloso.comとXアカウントで公開中。