Принцип единственной обязанности в WordPress

Разработчикам WordPress порой достаточно трудно расширить свои знания и навыки в сфере PHP. Не хватает полезных ресурсов или руководств для понимания существующих концепций. Всего несколько недель назад Натаниэль попросил помощи в Twitter по поводу опубликованного им вопроса на Stack Overflow.

single-responsibility

Он искал помощи в плане применения принципа единственной обязанности в WordPress. Я дал ему несколько советов, однако в самом конце дискуссии пообещал ему написать статью об этом. Данная статья представляет собой детальное разъяснение моих мыслей после диалога с Натаниэлем. Я надеюсь, что статья поможет вам решить аналогичные вопросы и улучшить свои PHP-навыки.

Вы как WordPress-разработчик, возможно, уже начали изучать объектно-ориентированное программирование. Возможно, вы даже слышали о том, что носит название SOLID. SOLID – достаточно объемная тема для обсуждений, поэтому мы рассмотрим только один ее аспект: принцип единственной обязанности (буква S в аббревиатуре SOLID).

SOLID это аббревиатура пяти основных принципов дизайна классов в объектно-ориентированном проектировании — Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion.

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

Разъяснение принципа единственной обязанности

Перед тем как мы начнем, давайте рассмотрим некоторые вещи, которые так или иначе связаны с принципом единственной обязанности.

Что представляет собой этот принцип?

Принцип единственной обязанности можно объяснить разными путями. Класс, который вы создали, должен иметь одну и только одну вещь. Он должен иметь внутри себя всю информацию, которая требуется, чтобы делать то, ради чего он был создан. Это означает, что вы не должны использовать глобальные переменные.

Почему это так сложно реализовать в WordPress?

Разработка WordPress изначально велась без учета объектного подхода. Девелоперы просто создали простые API, которые можно было легко использовать в WordPress. Эти API прекрасно подходили для неопытных PHP-разработчиков. Возможно, именно так вы и прикоснулись к PHP и программированию. Однако API могут сбить с толку, когда вы пытаетесь применять понятия объектно-ориентированного программирования. Это особенно верно для принципа единственной обязанности.

Что WordPress советует вам делать

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

Создание плагина таким путем приводит к появлению огромного класса, который содержит все ваши функции. Указанный подход не дает вам никаких преимуществ перед простым написанием функций с префиксом. Он также не следует за принципом единственной обязанности. У вашего класса нет цели, поскольку он делает абсолютно все.

Давайте применим принцип единственной обязанности

Теперь, когда мы обрисовали некоторые азы, давайте рассмотрим суть самого принципа. Мы поможем вам использовать принцип единственной обязанности для создания WordPress плагина. Я являюсь огромным поклонником мемов, поэтому давайте создадим небольшой WP Meme Shortcode плагин в качестве примера.

Вы можете найти полный код плагина на Github.

Деление классов по обязанностям

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

Точка входа

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

В точку входа не нужно добавлять все хуки и фильтры для всего плагина. Это выходит за рамки обязанности указанного класса. Обработка фильтров и хуков осуществляется отдельно, о чем мы поговорим далее в статье.

Я обычно называют класс точки входа как «Plugin». Такое решение может показаться странным, однако его можно логически объяснить: если WordPress должен знать про загрузку одного класса, plugin – вполне логичное название в данном контексте.

class WPMemeShortcode_Plugin
{
    /**
     * Loads the plugin into WordPress.
     */
    public static function load()
    {
        // ...
    }
}

Управление опциями

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

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

class WPMemeShortcode_Options
{
    /**
     * @var array
     */
    private $options;
 
    /**
     * Constructor.
     *
     * @param array $options
     */
    public function __construct(array $options = array())
    {
        $this->options = $options;
    }
 
    /**
     * Gets the option for the given name. Returns the default value if the
     * value does not exist.
     *
     * @param string $name
     * @param mixed  $default
     *
     * @return mixed
     */
    public function get($name, $default = null)
    {
        if (!$this->has($name)) {
            return $default;
        }
 
        return $this->options[$name];
    }
 
    /**
     * Checks if the option exists or not.
     *
     * @param string $name
     *
     * @return Boolean
     */
    public function has($name)
    {
        return isset($this->options[$name]);
    }
 
    /**
     * Sets an option. Overwrites the existing option if the name is already in use.
     *
     * @param string $name
     * @param mixed  $value
     */
    public function set($name, $value)
    {
        $this->options[$name] = $value;
    }
}

Страница в панели администратора

С помощью Settings API вы можете легко создать класс для обработки вашей администраторской страницы (страниц). Класс будет обрабатывать регистрацию всех параметров, а также их вывод.

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

class WPMemeShortcode_AdminPage
{
    /**
     * @var WPMemeShortcode_Options
     */
    private $options;
 
    /**
     * Constructor.
     *
     * @param WPMemeShortcode_Options $options
     */
    public function __construct(WPMemeShortcode_Options $options)
    {
        $this->options = $options;
    }
 
    /**
     * Adds the admin page to the menu.
     */
    public function addAdminPage()
    {
        add_options_page(__('WordPress Meme Shortcode', 'wp_meme_shortcode'), __('Meme Shortcode', 'wp_meme_shortcode'), 'install_plugins', 'wp_meme_shortcode', array($this, 'render'));
    }
 
    /**
     * Configure the option page using the settings API.
     */
    public function configure()
    {
        // Register settings
        register_setting('wp_meme_shortcode', 'wp_meme_shortcode');
 
        // General Section
        add_settings_section('wp_meme_shortcode_general', __('General', 'wp_meme_shortcode'), array($this, 'renderGeneralSection'), 'wp_meme_shortcode');
        add_settings_field('wp_meme_shortcode_size', __('Default Image Size', 'wp_meme_shortcode'), array($this, 'renderSizeField'), 'wp_meme_shortcode', 'wp_meme_shortcode_general');
    }
 
    /**
     * Renders the admin page using the Settings API.
     */
    public function render()
    {
        ?>
        <div class="wrap" id="wp-meme-shortcode-admin">
            <h2><?php _e('WordPress Meme Shortcode', 'wp_meme_shortcode'); ?></h2>
            <form action="options.php" method="POST">
                <?php settings_fields('wp_meme_shortcode'); ?>
                <?php do_settings_sections('wp_meme_shortcode'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Renders the general section.
     */
    public function renderGeneralSection()
    {
        ?>
        <p><?php _e('Configure WordPress Meme Shortcode.', 'wp_meme_shortcode'); ?></p>
        <?php
    }
 
    /**
     * Renders the size field.
     */
    public function renderSizeField()
    {
        ?>
        <input id="wp_meme_shortcode_size" name="wp_meme_shortcode[size]" type="number" value="<?php echo $this->options->get('size', '500'); ?>" />
        <?php
    }
}

Шорткоды

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

class WPMemeShortcode_Shortcode
{
    /**
     * @var WPMemeShortcode_Options
     */
    private $options;
 
    /**
     * Constructor.
     *
     * @param WPMemeShortcode_Options $options
     */
    public function __construct(WPMemeShortcode_Options $options)
    {
        $this->options = $options;
    }
 
    /**
     * Handles the output of the shortcode.
     *
     * @param array  $attributes
     * @param string $content
     */
    public function handle(array $attributes, $content = null)
    {
        // Do nothing if no ID is given or it is not numeric
        if (!isset($attributes['id']) || !is_numeric($attributes['id'])) {
            return $content;
        }
 
        // If no size is given or it is not a numeric value, get default.
        if (!isset($attributes['size']) || !is_numeric($attributes['size'])) {
            $attributes['size'] = $this->options->get('size', '500');
        }
 
        return "<img src=\"http://cdn.memegenerator.net/instances/{$attributes['size']}x/{$attributes['id']}.jpg\" />";
    }
}

Обработка хуков и фильтров

Вне зависимости от того, работаете ли вы над темой или плагином WordPress, вы должны использовать фильтры и хуки. Это основной принцип самой WordPress. В данном месте мы сталкиваемся с одной сложностью: как быть с фильтрами и хуками при использовании классов, содержащих код?

Как поступить

Два самых популярных пути работы с фильтрами и хуками:

  1. Помещение всех хуков и фильтров в конструктор
  2. Создание нового объекта. Использование этого объекта и регистрация всех хуков и фильтров за пределами класса.

Ни один из вариантов не является прекрасным. Создание объекта не должно включать в себя добавление хуков и фильтров по умолчанию. Могут быть ситуации (к примеру, с целью тестирования), когда вы хотите создать объект без добавления к нему хуков. Вынося код хуков и фильтров за пределы класса, вы лишаете класс его ключевой обязанности.

Использование статичных функций

Самый лучший способ решения этой проблемы – использование статичной функции в качестве произвольного конструктора. Функция создает экземпляр вашего класса и использует его для регистрации соответствующих хуков и фильтров. Это позволяет вам отделить регистрацию хуков и фильтров от создания вашего объекта. Я обычно называю такую функцию register или load.

class WPMemeShortcode_AdminPage
{
    /**
     * Register the admin page class with all the appropriate WordPress hooks.
     *
     * @param WPMemeShortcode_Options $options
     */
    public static function register(WPMemeShortcode_Options $options)
    {
        $page = new self($options);
 
        add_action('admin_init', array($page, 'configure'));
        add_action('admin_menu', array($page, 'addAdminPage'));
    }
 
    // ...
 
}

Статичные функции – это прекрасный способ изолировать ваш код в WordPress. Они позволяют вам заранее обрабатывать различную функциональность WordPress. Это отсылает к идее, что ваш класс должен иметь всю необходимую информацию внутри себя. Он не должен полагаться на внешнюю информацию в процессе работы.

class WPMemeShortcode_Options
{
    /**
     * Load the plugin options from WordPress.
     *
     * @return WPMemeShortcode_Options
     */
    public static function load()
    {
        $options = get_option('wp_meme_shortcode', array());
 
        return new self($options);
    }
 
    // ...
 
}

Вы должны использовать функции WordPress только тогда, когда они целесообразны в вашем классе. WordPress включает в себя массу полезных вспомогательных функций. Функции, такие как get_option, не помогут вашему классу справиться со своей задачей. Именно поэтому они являются идеальными кандидатами на выделение их в статичную функцию.

Почему это полезно для вас

Таким образом, исследовать придется довольно много всего. Это вас отпугивает? Возможно, вы даже подумали, что «овчинка выделки не стоит». Давайте развеем эти сомнения и приведем некоторые причины, почему следует позаботиться о принципе единственной обязанности.

Ваш код станет чище

Вы сможете взглянуть в свой код (или в код других людей) и сказать, что делает каждая функция и что поместить в нее.

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

Ваш код станет более надежным

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

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

Ваш код проще тестировать

Модульное тестирование – то, что делают далеко не все разработчики WordPress. Однако если ваши классы специализируются на одной вещи, их гораздо проще протестировать. Тестирование станет менее сложным. Использование статичного метода в качестве конструктора для изоляции некоторой функциональности WordPress также помогает с этим.

Источник: https://carlalexander.ca

Блог про WordPress
Комментарии: 3
  1. Алексей

    Спасибо. Полезная статья. Даже для тех людей, которые уже знакомы с php и wordpress.

  2. VideoStrimer

    По моему если соображаешь в ПЭХАПЭ, то лучше самому состряпать подобие движка. Как минимум, сайт на чистом HTML с инклюдами меню, шапки, футера и т. п.

    1. Дмитрий (автор)

      Ага, и с дырами, которые любой школьник сможет эксплуатировать.

Добавить комментарий

Получать новые комментарии по электронной почте.