Пишем плагин для WordPress: часть 3, собственное пространство имён (namespace) плагина
Как нам предписываем codex, мы должны сами озаботиться уникальностью идентификаторов в коде нашего плагина. WordPress не создаёт “автоматически” для нас собственного пространства имён (собственно говоря, это проблема PHP, а не wordpress). Приведу решение для организации собственного пространства имён в коде плагина.
Судя по коду плагинов, используемых мною в моём блоге, большинство разработчиков используют, как и предлагает кодекс, уникальные префиксы для идентификаторов. При таком стиле программирования для каждого плагина необходимо придумывать уникальные префиксы, заботиться о их уникальности (они же не GUID), что уже пахнет анахронизмом.
Однако существуют решения. Одно из них (использование классов) я опишу в этой статье, а с другим (использование анонимных функций, или lambda функций, что более правильно) можно ознакомиться в статье «AJAX, WordPress и счётчики: пишем свой плагин AJAX Яндекс.Метрика, отражаем AJAX-переходы в счётчике».
Использование классов PHP для изоляции пространства имён своего плагина ещё не вошло в обязанность, а использование в качестве hook (реакция на конкретные события wordpress) методов объектов и статических методов классов – пока редкость. Однако, как программист, избалованный ООП в целом и C++ в частности, решил попробовать при разработке своего плагина исповедовать ООП подход. Благо, PHP позволяет (гарантировано с версии 5.3) нам использовать в качестве callback функций и методы объектов, и статические методы классов.
Приведу цитату из кода своего плагина, использующего классы PHP для создания собственного пространства имён:
<?php add_action('plugins_loaded', array('cos_search_provider', 'getInstance')); class cos_search_provider { protected $_name = "COS Search provider"; protected $_namespace = __CLASS__; protected $_folder; protected $_domain; protected $_path; protected $_link = 'http://sergey-s-betke.blogs.novgaro.ru/ie-search-provider-wordpress-plugin/'; final public static function getInstance() { static $_instance = null; return $_instance ? $_instance : $_instance = new self; } final public function __clone () { trigger_error( 'This is singleton!', E_USER_ERROR ); } final private function __construct() { $this->_folder = dirname(plugin_basename(__FILE__)); $this->_domain = $this->_folder; $this->_path = WP_PLUGIN_DIR . '/' . $this->_folder . '/'; load_plugin_textdomain($this->_domain, false, $this->_folder . '/languages/'); add_action('admin_init', array($this, 'admin_init')); add_action('admin_menu', array($this, 'admin_menu_setup')); } public function admin_init() { register_setting( $this->_namespace, $this->_namespace . '_options', array($this, 'validate_options') ); } public function admin_menu_setup() { … } public function validate_options($options) { … } public function options_page() { … } } ?>
Кроме того, что мы использовали класс для создания пространства имён, мы также использовали для предопределённых действий wordpress в роли callback функций методы объекта (для действия plugins_loaded мы использовали статический метод класса, а для действий admin_init, admin_menu использовали методы объекта).
В этой статье я сознательно не останавливаюсь на назначении функции add_action. Об этом – позднее. В этой статье я лишь хотел продемонстрировать использование классов PHP для создания пространства имён плагина, а также возможности использования в качестве callback функций как статических методов класса (вызов add_action во второй строке примера), так и методов объектов (вызовы add_action в теле конструктора для действий admin_init и admin_menu, строки 29 и 30).
P.S. при использовании классов внимательно контролируйте область видимости Ваших методов. В частности, у меня возникла сложно диагностируемая проблема лишь потому, что для метода admin_init была установлена область видимости protected. Естественно, PHP при вызове callback функции, определённой как protected метод объекта, возвращает ошибку. Итак, следует помнить: область видимости всех методов, которые Вы планируете использовать в качестве callback функций, должна быть public.
Следует обратить внимание на поле _folder нашего собственного объекта. Как Вы понимаете, оно должно содержать наименование папки плагина, при этом формируем мы его исходя из имени php файла плагина (__FILE__). Безусловно, значения можно задать явно, но приведённый вариант существенно больше подходит под copy-paste вариант создания плагинов. И если бы PHP поддерживал макросы на уровне препроцессора – приведённый вариант замечательно лёг бы на макросы.
Из выше приведённого кода явно следует постулат: имя папки плагина должно в точности соответствовать имени (без расширения) php файла плагина. Почему это должно быть так, следует из вызова функции load_plugin_textdomain (её мы уже рассматривали при русификации плагинов, и подробнее рассмотрим позднее, при создании файлов текстовых ресурсов для нового плагина). Чётко видно, что в качестве относительного пути по отношению к WP_PLUGIN_DIR мы используем вычисленное через __FILE__ имя каталога (такая форма вызова load_plugin_textdomain поддерживается и рекомендуется с версии wordpress 2.7).
Также обращаю Ваше внимание на поле _namespace нашего объекта. Нам всё-таки потребуется уникальный префикс для работы с API wordpress при сохранении параметров плагина в базе данных. И в качестве этого префикса мы используем имя класса. Такой подход опять-таки хорошо подходит к copy-paste кодированию.
Теперь же приведу тот же код, но при этом ряд полей делаю статическими (ну уж если их природа действительно статическая – почему мы не сделать?):
<?php add_action('plugins_loaded', array('cos_search_provider', 'getInstance')); class cos_search_provider { private static $_name = 'COS Search provider'; private static $_namespace = __CLASS__; private static $_folder; private static $_domain; private static $_path; private static $_link = 'http://sergey-s-betke.blogs.novgaro.ru/ie-search-provider-wordpress-plugin/'; final public static function getInstance() { static $_instance = null; return $_instance ? $_instance : $_instance = new self; } final public function __clone () { trigger_error( 'This is singleton!', E_USER_ERROR ); } final private function __construct() { self::$_folder = dirname(plugin_basename(__FILE__)); self::$_domain = self::$_folder; self::$_path = WP_PLUGIN_DIR . '/' . self::$_folder . '/'; load_plugin_textdomain(self::$_domain, false, self::$_folder . '/languages/'); add_action('admin_init', array($this, 'admin_init')); add_action('admin_menu', array($this, 'admin_menu_setup')); } public function admin_init() { ... } public function admin_menu_setup() { ... } public function validate_options($options) { if (!is_array($options)) { $options = array( 'title' => get_bloginfo('name') ); } if ($options['title'] == '') $options['title'] = get_bloginfo('name'); return $options; } public function options_page() { ... } } ?>
Этот код работает не хуже. Целью опять-таки была демонстрация. Естественно, и некоторые методы напрашиваются как статические. На самом деле приведённый выше код далёк от идеала. Следует понимать, что в случае плагина (не виджета) класс вообще должен быть статическим. А вот в случае виджета методы и поля будут разделены. Те, что связаны с конкретным экземпляром виджета – динамические поля и методы, те, что связаны именно с классом и только с классом – статическими. Позднее доведу код до ума в этом плане. Ведь и конструктор следует править, совершенно ни к чему вообще создавать экземпляр в моём случае, кроме того, load_plugin_textdomain вызывать точно следует однократно, посему место этому вызов не в конструкторе. Итак, исправляем:
if (defined('ABSPATH') && defined('WPINC')) { global $wp_version; if ( version_compare($wp_version, "3.0", "<") ) { $pluginError = sprintf(__('COS Search Provider requires WordPress %s or newer. <a href="http://codex.wordpress.org/Upgrading_WordPress">Please update!</a>')); exit ($pluginError); } register_activation_hook ( __FILE__, array('cos_search_provider', 'activation' )); register_deactivation_hook ( __FILE__, array('cos_search_provider', 'deactivation' )); register_uninstall_hook ( __FILE__, array('cos_search_provider', 'uninstall' )); add_action('plugins_loaded', array('cos_search_provider', 'plugins_loaded')); } class cos_search_provider { private static $_name; private static $_namespace = __CLASS__; private static $_folder; private static $_domain; private static $_path; private static $_link = 'http://sergey-s-betke.blogs.novgaro.ru/ie-search-provider-wordpress-plugin/'; private static $options; public static function activation() { } public static function deactivation() { unregister_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); } public static function uninstall() { delete_option(self::$_namespace . '_options'); } public static function plugins_loaded() { self::$_folder = dirname(plugin_basename(__FILE__)); self::$_domain = self::$_folder; self::$_path = WP_PLUGIN_DIR . '/' . self::$_folder . '/'; add_action('init', array(__CLASS__, 'init')); } public static function init() { load_plugin_textdomain(self::$_domain, false, self::$_folder . '/languages/'); self::$_name = __('COS Search provider', self::$_domain); add_action('admin_init', array(__CLASS__, 'admin_init')); add_action('admin_menu', array(__CLASS__, 'admin_menu')); add_action('wp_head', array(__CLASS__, 'wp_head' )); } public static function admin_init() { ... } public static function option_control_title() { ... } public static function validate_options($options) { ... } public static function admin_menu() { ... } public static function options_page() { ... } public static function wp_head() { ... } }
Как видно, все признаки singleton мы убрали из класса, и вместо конструктора для инициализации используем статический метод класса. Также уже исключили ошибки, связанные с прямой загрузкой файла, не из wordpress (первое условие). Кроме того, разбили инициализацию на несколько этапов (часть – на plugins_loaded, часть – на init), как того и требует codex.
С организацией собственного пространства имён для плагина разобрались.
RSS комментарии
Обратная ссылка