Использование класса плагина внутри шаблона WordPress
Я пишу плагин для отправки приглашения другу, который открывает форму при клике на ссылку. Все функции я инкапсулировал в класс, следуя примеру плагина Report Broken Video от @toscho. Соответствующий код приведен ниже:
/*
Plugin Name: Send Invitation
Plugin URI: http://w3boutique.net
Description: Отправляет ссылку на текущую страницу другу по email
Author: Nandakumar Chandrasekhar
Version: 0.1
Author URI: http://w3boutique.net/about-nanda.html
License: GPL2
*/
// include() или require() необходимых файлов
// Настройки и конфигурация
define('SEND_INVITATION_MIN_WORDPRESS_VERSION', '3.1.1');
define('SEND_INVITATION_PLUGIN_URL', plugins_url('', __FILE__));
add_action( 'init', array( 'SendInvitation', 'nc_sendinvitation_init' ) );
class SendInvitation {
protected $nonce_name = 'nc_sendinvitation';
protected $post_url = '';
public static function nc_sendinvitation_init() {
new self;
}
public function __construct() {
add_action( 'init', array(&$this, 'nc_sendinvitation_head' ));
add_action( 'init', array(&$this, 'nc_sendinvitation_check_wordpress_version' ));
add_action( 'init', array(&$this, 'nc_sendinvitation_form_action' ));
//$this->post_url = $this->nc_sendinvitation_get_post_url();
}
public function nc_sendinvitation_head() {
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'send_invitation_js',
plugins_url( 'js/send-invitation.js', __FILE__ ),
array( 'jquery' ) );
wp_enqueue_style( 'send_invitation_css',
plugins_url( 'css/send-invitation.css', __FILE__ ) );
}
public function nc_sendinvitation_check_wordpress_version() {
global $wp_version;
$exit_msg = 'Для работы Send Invitation требуется версия '
. SEND_INVITATION_MIN_WORDPRESS_VERSION
. 'или новее <a href="http://codex.wordpress.org/Upgrading_WordPress">Пожалуйста,
обновите WordPress!</a>';
if ( version_compare( $wp_version, SEND_INVITATION_MIN_WORDPRESS_VERSION, '<') )
{
exit( $exit_msg );
}
}
public function nc_sendinvitation_form_action() {
$action = '';
if ( $_SERVER['REQUEST_METHOD'] != 'POST' )
{
$action = $this->nc_sendinvitation_get_form();
}
else if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
$action = $this->nc_sendinvitation_handle_submit();
}
return $action;
}
public function nc_sendinvitation_get_form() {
// Буферизация вывода для сохранения содержимого include в переменную
ob_start();
include('send-invitation-form.php');
$send_invitation_link = ob_get_clean();
return $send_invitation_link;
}
public function nc_sendinvitation_handle_submit() {
if ( isset( $_POST['form_type'] ) && ( $_POST['form_type'] == 'nc_sendinvitation' ) ) {
$to = 'navanitachora@gamil.com';
$subject = 'Приглашение на SwanLotus';
$message = 'Navanitachora приглашает вас посмотреть эту ссылку';
wp_mail($to, $subject, $message);
$result = 'Письмо успешно отправлено';
}
else {
$result = $this->nc_sendinvitation_get_form();
}
return $result;
}
public function nc_sendinvitation_get_post_url() {
global $post;
$blog_id = get_option('page_for_posts');
$post_id = '';
if (is_home($blog_id)) {
$post_id = $blog_id;
} else {
$post_id = $post->ID;
}
return get_permalink($post_id);
}
}
/* Конец файла */
?>
Я не понимаю, как использовать этот класс в своем шаблоне, чтобы отобразить форму. Я знаю, что нужно создать экземпляр класса, но не уверен, куда поместить этот код и как получить доступ к объекту для использования в шаблоне. У меня есть знания ООП, но я не использовал его в таком контексте и мне нужно пошаговое руководство.
Заранее спасибо.

Лучший способ использовать ваш класс без необходимости знать объект — это действие (action). Вы регистрируете действие до загрузки файлов темы для отображения, а WordPress позаботится обо всем остальном.
Пример кода:
<?php # -*- coding: utf-8 -*-
/**
* Plugin Name: Plugin Action Demo
*/
add_action( 'init', array ( 'Plugin_Action_Demo', 'init' ) );
class Plugin_Action_Demo
{
/**
* Создает новый экземпляр.
*
* @wp-hook init
* @see __construct()
* @return void
*/
public static function init()
{
new self;
}
/**
* Регистрирует действие. Может выполнять дополнительные магические вещи.
*/
public function __construct()
{
add_action( 'plugin_action_demo', array ( $this, 'print_foo' ), 10, 1 );
}
/**
* Выводит 'foo' указанное количество $times.
*
* Использование:
* <code>do_action( 'plugin_action_demo', 50 );</code>
*
* @wp-hook plugin_action_demo
* @param int $times
* @return void
*/
public function print_foo( $times = 1 )
{
print str_repeat( ' foo ', (int) $times );
}
}
Теперь вы можете вызвать do_action( 'plugin_action_demo', 50 );
где-нибудь в вашей теме или в другом плагине, и вам больше не нужно заботиться о внутренней работе класса.
Если вы деактивируете плагин, вы все равно в безопасности: WordPress просто игнорирует неизвестные действия, и вызов do_action()
не причинит вреда. Кроме того, другие плагины могут удалить или заменить действие, поэтому вы создали мини-API всего с одним add_action()
.
Вы также можете реализовать синглтон:
<?php # -*- coding: utf-8 -*-
/**
* Plugin Name: Plugin Singleton Demo
*/
class Plugin_Singleton_Demo
{
protected static $instance = NULL;
/**
* Создает новый экземпляр, если его еще нет.
*
* @wp-hook init
* @return object
*/
public static function get_instance()
{
NULL === self::$instance and self::$instance = new self;
return self::$instance;
}
/**
* Недоступен извне.
*/
protected function __construct() {}
/**
* Выводит 'foo' указанное количество $times.
*
* @param int $times
* @return void
*/
public function print_foo( $times = 1 )
{
echo str_repeat( ' foo ', (int) $times );
}
}
Теперь метод print_foo()
доступен через:
Plugin_Singleton_Demo::get_instance()->print_foo();
Я не рекомендую использовать паттерн Singleton. У него есть несколько серьезных недостатков.

Из чистого любопытства, какова цель plugin_action_demo
в add_action()
? Как do_action( 'print_foo', 50 );
узнаёт, что действие — это plugin_action_demo
, или это имя не имеет значения?

Спасибо, это работает, осталось только добавить немного валидации, и я закончу. Спасибо @toscho, ты многому меня научил. :-)

@tosco Нашел твой ответ, когда искал статьи о синглтонах для плагинов WordPress. Ты же понимаешь, что в твоем рекомендуемом ответе ты используешь синглтон, просто не форсируешь его? Представь, если темист вызовет new Plugin_Action_Demo
?? Любой, кто использует do_action('plugin_action_demo')
, вызовет два (2) вызова Plugin_Action_Demo->print_foo()
, а это не то, что нужно. Шумиха вокруг синглтонов игнорирует тот факт, что есть подходящие случаи их использования. Кстати, и @ericmann, и я сейчас ведем блоги, где выступаем за использование синглтонов для пространств имен плагинов WordPress: он на eamann.com, а я на hardcorewp.com.

@MikeSchinkel Подход, который я сейчас рекомендую, такой: https://gist.github.com/3804204 – к нему можно обращаться как к синглтону, но не обязательно.

@tocho Хорошо, я вижу, что тебе не нужно обращаться к твоему примеру как к синглтону, но в чем смысл? В каких случаях это может быть полезно?

@MikeSchinkel Юнит-тесты и дочерние классы. И нет, я не изменяю видимость в дочернем классе, если меня не заставят. :)

@toscho Ты действительно пишешь юнит-тесты для классов, используемых в основе твоих плагинов? Если да, то как ты справляешься с хуками, которые нельзя протестировать без загрузки страницы? Разве это не делает юнит-тестирование этих классов бессмысленным? И я без проблем создаю подклассы для своих синглтонов, более того, это ключевой элемент моей архитектуры; почему это может быть проблемой? Кстати, я собираюсь опубликовать пост в защиту синглтонов и напишу продолжение про юнит-тестирование. Но если у тебя есть веские аргументы против, я хотел бы узнать об этом до публикации. Заранее спасибо.

@MikeSchinkel Я использую http://core.trac.wordpress.org/browser/tests – скорость загрузки страницы не проблема. И мне не нравится идея Эрика тестировать подкласс, который не является реальным рабочим кодом и отличается в поведении. Надо бы написать об этом в блоге, комментарии не для этого. :)

@toscho - Буду ждать твою запись в блоге. Кстати, я не понял, что ты имел в виду под "Я не меняю видимость в дочернем классе, если меня к этому не вынуждают."

@toscho Также, когда ты сказал "загрузка страницы не проблема", мне кажется, ты недопонял; я имел в виду, что загрузка страницы — это практически единственный способ протестировать класс, используемый для подключения обработчиков хуков к методам, т.е. что классическое модульное тестирование здесь не имеет смысла.
