タグ: プラグイン開発

  • 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