変更・保守しやすいPowerCMS Xプラグインを作るためのクラス分割設計パターン

公開

PowerCMS Xのプラグイン作成時、実現したい機能によってはプラグインのコードが長くなることがあります。機能を実現して終わりではなく、その後長く続く運用フェーズで保守しやすい・変更しやすい状態を求め、プラグインのコードを役割単位で分割することを考えました。

例として今回作成するMySaaSプラグインは、ユーザー登録フォームに投稿するとコンタクトモデルに情報を保存した後でユーザーに登録確認メールを送信します。ユーザー登録確認メールにはトークンを含むURLが記載されており、URLにアクセスすると認証を行った後ユーザーとして登録されます。また、ユーザーは自分の操作により退会することができます。

クラスに分ける

大きく分けて3つあるまとまり、すなわちユーザー確認処理・ユーザー登録処理・退会処理をそれぞれクラスにして記述することにします。

.
├── classes
│   ├── CreateAccountService.php
│   ├── DeleteAccountService.php
│   └── UserConfirmationService.php
├── config.json
└── MySaaS.php

ユーザー登録処理を行うCreateAccountServiceクラスは以下のようにコードを記述していきます。クラス名が他のプラグインとバッティングする可能性がゼロではないため、namespace MySaaS;も指定しました。このクラスにはユーザー登録処理のみ記述するので、ユーザー登録処理の変更がユーザー確認処理に及ぶことはないですし、逆も然りなので理解しやすい・変更しやすい状態です。

<?php
namespace MySaaS;

use Prototype;
use PADOMySQL;

require_once __DIR__ . DS . '..' . DS . 'MySaaS.php';

/**
 * アカウント作成サービス
 */
class CreateAccountService {

    /** @var \MySaaS */
    private $plugin;

    /**
     * CreateAccountService constructor.
     *
     * @param \MySaaS $plugin
     */
    public function __construct( \MySaaS $plugin ) {
        $this->plugin = $plugin;
    }

    /**
     * トークンを検証
     *
     * @param Prototype $app
     * @return session|false
     */
    private function verify_token( Prototype $app ): \session | false {
        $token = $app->param( 'token' );
        
        // トークン検証処理

        return $session;
    }

    /**
     * サインアップ処理
     *
     * @param Prototype $app
     */
    public function handle_signup( Prototype $app ): void {
        $session = $this->verify_token( $app );
        if ( ! $session ) {
            return;
        }

        $plan_id = $this->plugin->get_config_value( 'mysaas_free_plan_id' );

        // 以下サインアップ処理が続く
    }

}

プラグインのファイルから作成したクラスを呼び出す

そして、PowerCMS Xがオートロードしプラグインとして認識するクラスファイルMySaaS.phpからCreateAccountServiceクラスを呼び出すようにします。classesディレクトリのクラスはオートロードはされないのでrequire_onceにファイルを指定します。また、プラグイン設定を取得するPTPluginクラスのメソッドget_config_valueをCreateAccountServiceクラスでも利用したいので、MySaaSクラスのインスタンスを渡すようにしました。

<?php
require_once LIB_DIR . 'Prototype' . DS . 'class.PTPlugin.php';
require_once 'classes' . DS . 'CreateAccountService.php';

class MySaaS extends PTPlugin {

    /**
     * @var MySaaS\CreateAccountService
     */
    private $create_account_service;

    /**
     * MySaaS constructor.
     */
    public function __construct () {
        parent::__construct();

        $this->create_account_service = new MySaaS\CreateAccountService( $this );
    }

    /**
     * MySaaS_signupモード
     *
     * @param Prototype $app
     */
    public function mysaas_signup( Prototype $app ): void {
        $this->create_account_service->handle_signup( $app );
    }

}

メモ

プラグインのconfig.jsonMySaaSとは別のクラスを指定すれば良いかも?と思いましたが、プラグインとして認識されなくなったので利用できませんでした。

"hooks": {
    "mysaas_pre_run": {
        "pre_run": {
            "component": "MySaaSAnotherClass",
            "priority": 1,
            "method": "pre_run"
        }
    }
},

他の参考実装

昨年、interfaceを利用した実装例も公開しています。