WordPress プラグイン開発入門:OOP設計でメンテナブルなプラグインを作る

WordPressプラグインの開発は、functions.phpにコードを追加する延長線上にありますが、本格的なプラグインはOOP(オブジェクト指向)で設計すべきです。この記事では、保守性が高く拡張しやすいプラグインの作り方を解説します。

プラグインのディレクトリ構成

my-awesome-plugin/
├── my-awesome-plugin.php    # メインファイル(エントリポイント)
├── includes/
│   ├── class-plugin.php     # プラグインのメインクラス
│   ├── class-admin.php      # 管理画面の処理
│   ├── class-api.php        # REST APIエンドポイント
│   └── class-db.php         # データベース操作
├── admin/
│   ├── views/               # 管理画面テンプレート
│   ├── css/
│   └── js/
├── public/
│   ├── css/
│   └── js/
├── languages/               # 翻訳ファイル
└── readme.txt               # WordPress.org用

メインファイル

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: プラグインの説明
 * Version: 1.0.0
 * Author: Your Name
 * Text Domain: my-awesome-plugin
 */

if (!defined("ABSPATH")) exit;

define("MAP_VERSION", "1.0.0");
define("MAP_PLUGIN_DIR", plugin_dir_path(__FILE__));
define("MAP_PLUGIN_URL", plugin_dir_url(__FILE__));

require_once MAP_PLUGIN_DIR . "includes/class-plugin.php";

// プラグイン初期化
function map_init() {
    return MyAwesomePluginPlugin::get_instance();
}
add_action("plugins_loaded", "map_init");

// アクティベーション・ディアクティベーション
register_activation_hook(__FILE__, ["MyAwesomePluginPlugin", "activate"]);
register_deactivation_hook(__FILE__, ["MyAwesomePluginPlugin", "deactivate"]);

プラグインのメインクラス(シングルトン)

<?php
namespace MyAwesomePlugin;

class Plugin {
    private static $instance = null;

    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->load_dependencies();
        $this->init_hooks();
    }

    private function load_dependencies() {
        require_once MAP_PLUGIN_DIR . "includes/class-admin.php";
        require_once MAP_PLUGIN_DIR . "includes/class-api.php";
        require_once MAP_PLUGIN_DIR . "includes/class-db.php";
    }

    private function init_hooks() {
        // 管理画面
        if (is_admin()) {
            new Admin();
        }
        // REST API
        add_action("rest_api_init", [new Api(), "register_routes"]);
        // フロントエンド
        add_action("wp_enqueue_scripts", [$this, "enqueue_public_assets"]);
    }

    public function enqueue_public_assets() {
        wp_enqueue_style(
            "map-public",
            MAP_PLUGIN_URL . "public/css/style.css",
            [],
            MAP_VERSION
        );
        wp_enqueue_script(
            "map-public",
            MAP_PLUGIN_URL . "public/js/main.js",
            [],
            MAP_VERSION,
            true
        );
        // JSにデータを渡す
        wp_localize_script("map-public", "mapConfig", [
            "ajaxUrl" => admin_url("admin-ajax.php"),
            "apiUrl" => rest_url("my-plugin/v1/"),
            "nonce" => wp_create_nonce("wp_rest"),
        ]);
    }

    public static function activate() {
        Db::create_tables();
        flush_rewrite_rules();
    }

    public static function deactivate() {
        flush_rewrite_rules();
    }
}

管理画面クラス

<?php
namespace MyAwesomePlugin;

class Admin {
    public function __construct() {
        add_action("admin_menu", [$this, "add_menu"]);
        add_action("admin_init", [$this, "register_settings"]);
    }

    public function add_menu() {
        add_options_page(
            "My Plugin設定",
            "My Plugin",
            "manage_options",
            "my-awesome-plugin",
            [$this, "render_settings_page"]
        );
    }

    public function register_settings() {
        register_setting("map_settings", "map_api_key");
        register_setting("map_settings", "map_enabled");
    }

    public function render_settings_page() {
        include MAP_PLUGIN_DIR . "admin/views/settings.php";
    }
}

データベースクラス

<?php
namespace MyAwesomePlugin;

class Db {
    public static function create_tables() {
        global $wpdb;
        $table = $wpdb->prefix . "map_logs";
        $charset = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS $table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) DEFAULT NULL,
            action varchar(50) NOT NULL,
            data longtext,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY user_id (user_id),
            KEY created_at (created_at)
        ) $charset;";

        require_once ABSPATH . "wp-admin/includes/upgrade.php";
        dbDelta($sql);
    }

    public static function insert_log($action, $data = null) {
        global $wpdb;
        return $wpdb->insert(
            $wpdb->prefix . "map_logs",
            [
                "user_id" => get_current_user_id(),
                "action" => $action,
                "data" => is_array($data) ? json_encode($data) : $data,
            ],
            ["%d", "%s", "%s"]
        );
    }
}

セキュリティのベストプラクティス

  • ABSPATH チェック: 全PHPファイルの先頭で if (!defined("ABSPATH")) exit; を記述
  • Nonceの検証: フォーム送信やAjaxリクエストでは必ずnonce検証を行う
  • 権限チェック: current_user_can() で適切な権限を確認
  • サニタイズ: 入力値は sanitize_text_field()、出力は esc_html() で処理
  • SQLインジェクション対策: $wpdb->prepare() を使用

まとめ

OOP設計でプラグインを作ることで、コードの見通しが良くなり、チーム開発やメンテナンスが格段に楽になります。最初は少し面倒に感じるかもしれませんが、一度フレームワークを作ってしまえば、新しいプラグインでも使い回せます。WordPress公式ディレクトリへの公開を目指す場合も、この設計であればレビューを通過しやすいでしょう。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

IP: 取得中...
216.73.216.31216.73.216.31