Часть 5 — WordPress и объектно-ориентированное программирование: пример WordPress — реализация: меню администрирования

Опубликовано: 2022-02-04

В нашей предыдущей статье об объектно-ориентированном программировании мы обсуждали дизайн, который мы в итоге придумали для нашего объектно-ориентированного плагина.

Теперь мы перейдем к самой захватывающей части, где мы углубимся в то, как мы это реализовали!

Мы проведем вас через некоторые части реализации, шаг за шагом, рассказывая о самых основах объектно-ориентированного программирования, синтаксисе PHP, некоторых основных концепциях и даже рассмотрим принципы SOLID.

К концу этой статьи вы, надеюсь, лучше поймете ООП и будете в восторге от написания собственных объектно-ориентированных плагинов!

Начиная

Мы предполагаем, что вы знакомы с разработкой плагинов для WordPress в целом, поэтому мы сосредоточимся на объектно-ориентированных аспектах нашего плагина. Если вы новичок в разработке плагинов или вам нужно освежить свои знания, вам следует сначала узнать, как создать свой первый плагин WordPress.

Давайте начнем, как всегда, с создания нового файла prsdm-limit-login-attempts.php в каталоге нашего плагина (то есть /wp-content/plugins/prsdm-limit-login-attempts).

Основной файл плагина будет включать заголовок плагина, с которым вы уже знакомы:

 /** * Plugin Name: PRSDM Limit Login Attempts * Plugin URI: https://pressidium.com * Description: Limit rate of login attempts, including by way of cookies, for each IP. * Author: Pressidium * Author URI: https://pressidium.com * Text Domain: prsdm-limit-login-attempts * License: GPL-2.0+ * Version: 1.0.0 */

И простой оператор if для предотвращения прямого доступа к нему.

 if ( ! defined( 'ABSPATH' ) ) { exit; }

Это все, что нам нужно на данный момент. Мы вернемся к этому файлу позже!

Создание административного меню

Когда вы разрабатываете плагин, вам часто нужно предоставить пользователям способ его настройки. Вот где появляется страница настроек. Чтобы создать ее, мы собираемся добавить меню администрирования, которое использует API настроек WordPress.

Итак, давайте начнем думать о том, как будет выглядеть наш объектно-ориентированный API.

В идеале мы хотели бы создать экземпляр нашей Pressidium_LLA_Settings_Page и покончить с этим. Чтобы создать экземпляр класса, необходимо использовать ключевое слово new .

 new Pressidium_LLA_Settings_Page();

Теперь давайте подумаем, как будет выглядеть наш класс Pressidium_LLA_Settings_Page .

Мы начнем с создания нового класса, используя ключевое слово class :

 class Pressidium_LLA_Settings_Page {}

Имя нашего класса должно начинаться с уникального идентификатора Pressidium_LLA_ , чтобы предотвратить конфликты имен с другими плагинами WordPress. Префиксы предотвращают перезапись и/или случайный вызов другими плагинами наших классов. Пока наши имена классов уникальны или мы используем пространства имен, конфликтов с другими плагинами не будет.

Конструктор

Теперь мы подключимся к admin_menu и admin_init. Для простоты мы просто вызовем add_action() в нашем конструкторе (спойлер: мы изменим это позже).

 class Pressidium_LLA_Settings_Page { /** * Settings_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } }

Классы, имеющие конструктор, вызывают этот метод при создании экземпляра объекта. Таким образом, __construct() отлично подходит для любой инициализации, которую мы можем захотеть выполнить.

Давайте подробнее рассмотрим наши вызовы add_action() . Если вы уже разрабатывали плагины для WordPress, вы могли ожидать чего-то подобного:

 add_action( 'admin_menu', 'my_plugin_prefix_add_page' );

Но вместо этого у нас есть:

 add_action( 'admin_menu', array( $this, 'add_page' ) );

Вы можете запутаться в использовании массива здесь. Всякий раз, когда мы хотим передать метод экземпляра объекта в качестве обратного вызова/вызываемого объекта, мы можем использовать массив, содержащий объект с индексом 0 и имя метода с индексом 1.

Что такое $это?

Это псевдопеременная, доступная при вызове метода из контекста объекта. $this значение вызывающего объекта. В данном случае $this является экземпляром Pressidium_LLA_Settings_Page .

Кроме того, все наши «функции» теперь являются методами, заключенными в класс, поэтому нет необходимости добавлять префикс к именам наших методов.

Пространства имен

Пространства имен в PHP позволяют нам группировать связанные классы, интерфейсы, функции и т. д., предотвращая коллизии имен между нашим кодом и внутренними PHP или сторонними классами/функциями.

Давайте продолжим и воспользуемся ими, чтобы нам не нужно было ставить префикс ни одному из наших классов в дальнейшем.

Мы объявим пространство имен, используя ключевое слово namespace .

 namespace Pressidium;

Пространства имен могут быть определены с подуровнями.

 namespace Pressidium\Limit_Login_Attempts;

Поскольку мы создаем страницу настроек, мы объявим подпространство имен «pages», чтобы сгруппировать вместе все, что связано со страницами администрирования.

 namespace Pressidium\Limit_Login_Attempts\Pages;

Наконец-то мы можем избавиться от префикса Pressidium_LLA_ !

 namespace Pressidium\Limit_Login_Attempts\Pages; class Settings_Page { // ...

Другой плагин WordPress, содержащий класс Settings_Page , больше не является проблемой, поскольку его класс и наш класс не будут находиться в одном и том же пространстве имен.

При создании экземпляра нашей страницы Settings_Page в том же пространстве имен мы можем его опустить:

 namespace Pressidium\Limit_Login_Attempts\Pages; $settings_page = new Settings_Page();

При создании экземпляра нашей страницы Settings_Page за пределами ее пространства имен мы должны указать ее следующим образом:

 namespace Another\Namespace; $settings_page = new \Pressidium\Limit_Login_Attempts\Pages\Settings_Page();

В качестве альтернативы мы могли бы импортировать наш класс с use оператора use:

 use Pressidium\Limit_Login_Attempts\Pages\Settings_Page; $settings_page = new Settings_Page();

Добавление обратных вызовов ловушек

Теперь давайте объявим эти add_page() и register_sections() .

 class Settings_Page { /** * Settings_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } /** * Add this page as a top-level menu page. */ public function add_page() { // TODO: Implement this method. } /** * Register sections. */ public function register_sections() { // TODO: Implement this method. } }

Наш метод add_page() будет просто вызывать функцию WordPress add_menu_page().

 public function add_page() { add_menu_page( __( 'Limit Login Attempts Settings', 'prsdm-limit-login-attempts' ), __( 'Limit Login Attempts', 'prsdm-limit-login-attempts' ), 'manage_options', 'prsdm_limit_login_attempts_settings', array( $this, 'render' ), 'dashicons-shield-alt', null ); }

Это кажется запутанным способом разработки плагинов WordPress. Это просто вызов функций WordPress с дополнительными шагами.

Ну, это не совсем «многоразовое использование», нам все равно придется писать весь этот дополнительный код для каждого административного меню/страницы, которую мы хотим добавить.

Рефакторинг

Давайте продолжим и немного рефакторим наш код, чтобы воспользоваться преимуществами объектно-ориентированного программирования и сделать наш код пригодным для повторного использования . Мы начнем с замены наших жестко заданных значений в add_page() несколькими методами, например:

 public function add_page() { add_menu_page( $this->get_page_title(), // page_title $this->get_menu_title(), // menu_title $this->get_capability(), // capability $this->get_slug(), // menu_slug array( $this, 'render' ), // callback function $this->get_icon_url(), // icon_url $this->get_position() // position ); }

Мы определим эти методы как protected , чтобы к ним можно было получить доступ только внутри самого класса и его дочерних/родительских классов.

 protected function get_page_title() { /* ... */ } protected function get_menu_title() { /* ... */ } protected function get_capability() { /* ... */ } protected function get_slug() { /* ... */ } protected function get_icon_url() { /* ... */ } protected function get_position() { /* ... */ }

Большой! Теперь мы можем использовать этот класс как многоразовый универсальный класс для расширения.

Редизайн

Мы говорили вам, что это, вероятно, должно было случиться в конце концов. Вот мы и переосмыслили дизайн нашего класса при его создании.

Поскольку это будет наш базовый класс , мы переименуем его в более общее имя, например Admin_Page . Пока это выглядит так:

 class Admin_Page { /** * Admin_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } /** * Add this page as a top-level menu page. */ public function add_page() { add_menu_page( $this->get_page_title(), // page_title $this->get_menu_title(), // menu_title $this->get_capability(), // capability $this->get_slug(), // menu_slug array( $this, 'render' ), // callback function $this->get_icon_url(), // icon_url $this->get_position() // position ); } /** * Register sections. */ public function register_sections() { // TODO: Implement this method. } protected function get_page_title() { /* ... */ } protected function get_menu_title() { /* ... */ } protected function get_capability() { /* ... */ } protected function get_slug() { /* ... */ } protected function get_icon_url() { /* ... */ } protected function get_position() { /* ... */ } }

Теперь мы можем создать отдельную страницу Settings_Page , которая расширяет базовый класс Admin_Page .

 class Settings_Page extends Admin_Page { // ... }

Это отличный пример наследования , одной из основных концепций объектно-ориентированного программирования. При расширении класса дочерний класс — в данном случае Settings_Page — наследует все общедоступные и защищенные методы, свойства и константы родительского класса.

Мы можем использовать это и установить некоторые значения по умолчанию. Например, мы установим общий значок для всех страниц меню, определив наш get_icon_url() следующим образом:

 class Admin_Page { // ... /** * Return the menu icon to be used for this menu. * * @link https://developer.wordpress.org/resource/dashicons/ * * @return string */ protected function get_icon_url() { return 'dashicons-admin-generic'; } }

Если класс не переопределит эти методы, они сохранят свою первоначальную функциональность. Таким образом, по умолчанию все дочерние классы будут использовать этот общий значок.

Однако, если мы хотим установить другую иконку для определенной страницы меню, мы можем просто переопределить метод get_icon_url() в нашем дочернем классе, например так:

 class Settings_Page extends Admin_Page { protected function get_icon_url() { return 'dashicons-shield-alt'; } }

Однако есть некоторые значения, которые должны быть разными для каждого дочернего класса. Например, название меню — четвертый аргумент add_menu_page() — должно быть уникальным для каждой страницы меню.

Если бы мы определили этот метод в нашем базовом классе Admin_Page , нам понадобился бы способ убедиться, что каждый дочерний класс переопределяет этот метод. Что ж, мы можем сделать что-то еще лучше. Мы можем объявить сигнатуру метода и полностью пропустить его реализацию.

Введите абстрактные методы!

Абстрактные классы и методы

Методы, определенные как абстрактные , просто объявляют сигнатуру метода и не могут определить его реализацию.

 /** * Return page slug. * * @return string */ abstract protected function get_slug();

Любой класс, содержащий хотя бы один абстрактный метод, также должен быть абстрактным. Это означает, что наш класс Admin_Page также должен быть определен как абстрактный.

 abstract class Admin_Page { // ...

Здесь также важно указать, что классы, определенные как абстрактные, не могут быть созданы. Таким образом, мы больше не можем напрямую создавать экземпляр Admin_Page .

Вот также визуализация класса:

При наследовании от абстрактного класса дочерний класс должен определить все методы, помеченные как абстрактные в объявлении родительского класса. Это означает, что наша страница Settings_Page должна реализовать метод get_slug() .

 class Settings_Page extends Admin_Page { // ... protected function get_slug() { return 'prsdm_limit_login_attempts_settings'; } // ... }

Точно так же мы должны реализовать остальные защищенные методы, необходимые для add_page() .

Прежде чем перейти к тому, как мы будем регистрировать разделы и поля страницы администратора и отображать их содержимое, давайте немного поговорим о настройках в WordPress.

API настроек

Мы предполагаем, что вы уже знакомы с API настроек. Но на всякий случай вот суть:

  • settings_fields() — выводит поля nonce, action и option_page для страницы настроек. В основном, скрытые поля формы.
  • do_settings_sections() — выводит все разделы настроек (и их поля), добавленные на определенную страницу настроек.
  • add_settings_section() — Добавляет новый раздел на страницу настроек.
  • add_settings_field() — Добавляет новое поле в раздел страницы настроек.
  • register_setting() — регистрирует параметр и его данные.

Если вы еще не знакомы с этим, вы можете приостановить чтение этой статьи и прочитать нашу статью о том, как создать страницу настроек для пользовательского плагина.

Теперь, когда мы находимся на той же странице, давайте вернемся к нашему методу register_sections() . Мы снова должны сделать шаг назад и подумать о нашем API.

Поскольку мы определили метод add_page() в классе Admin_Page , мы также определим там и метод render() . Мы передадим возвращаемые значения других наших методов в качестве аргументов функциям WordPress.

 abstract class Admin_Page { // ... /** * Render this admin page. */ public function render() { ?> <div class="wrap"> <form action="options.php" method="post"> <h1><?php echo esc_html( $this->get_page_title() ); ?></h1> <?php settings_fields( $this->get_slug() ); do_settings_sections( $this->get_slug() ); submit_button( __( 'Change Options', 'prsdm-limit-login-attempts' ) ); ?> </form> </div> <?php } }

Таким образом, нам больше никогда не придется напрямую возиться с этими функциями WordPress. Это связано с тем, что любая административная страница, которую мы можем добавить в будущем, будет построена с помощью дочернего класса, как и Settings_Page , и ее рендеринг будет выполняться с помощью унаследованного метода render() родительского класса Admin_Page .

Вывод

Большой! Мы создали классы, отвечающие за регистрацию меню администрирования и добавление страницы настроек.

В следующей статье серии мы продолжим создавать нашу страницу настроек и зарегистрировать ее разделы, поля и элементы.

Нажмите здесь, чтобы прочитать часть 6 в нашей серии объектно-ориентированного программирования

Смотрите также

  • WordPress и объектно-ориентированное программирование — обзор
  • Часть 2 — WordPress и объектно-ориентированное программирование: пример из реальной жизни
  • Часть 3 — WordPress и объектно-ориентированное программирование: пример WordPress — определение области
  • Часть 4 — WordPress и объектно-ориентированное программирование: пример WordPress — дизайн