Как нам предписываем 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.

С организацией собственного пространства имён для плагина разобрались.

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка