Статьи

Объектно-ориентированная автозагрузка в WordPress, часть 1

Недавно я завершил серию, в которой я рассмотрел пространства имен и автозагрузку в WordPress. Если вы не знакомы ни с одним из вышеперечисленных терминов, я рекомендую ознакомиться с серией.

Суть того, что вы можете узнать, заключается в следующем:

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

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

Это не значит, что автозагрузчик плох или что он не работает. Если вы скачали плагин, запустили его, либо следовали и написали свой собственный автозагрузчик, то вы знаете, что он действительно работает.

Но в серии, которая фокусируется на пространствах имен — то, что является неотъемлемой частью объектно-ориентированного программирования — я не мог не чувствовать себя некомфортно, оставляя автозагрузчик в его окончательном состоянии в конце серии.

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

В частности, мы будем говорить о концепции:

  • интерфейсы
  • реализация интерфейса
  • принцип единоличной ответственности
  • и другие принципы и идеи, лежащие в основе объектно-ориентированного программирования

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

С этим сказал, давайте начнем.

Как и почти в каждом посте, который я пишу, мне нравится делать две вещи:

  1. Определите дорожную карту того, куда мы идем.
  2. Дайте вам все, что вам нужно знать, чтобы ваша машина заработала.

Прежде чем мы начнем писать код, давайте сделаем это сейчас.

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

Если у вас нет копии этого плагина, вы можете скачать его копию ; однако я буду делиться полными примерами кода, комментариями и объяснениями в каждом уроке.

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

Для начала вам понадобятся следующие инструменты:

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

После этого мы готовы начать говорить об интерфейсах и принципе единой ответственности.

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

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

Есть ли более формальное определение? Конечно. Википедия предлагает один:

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

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

Опять же, мы работаем с PHP. Так что же может предложить руководство по PHP по этой теме?

Объектные интерфейсы позволяют вам создавать код, который определяет, какие методы должен реализовывать класс, без необходимости определять, как эти методы обрабатываются.

На мой взгляд, это действительно хорошее определение. Это просто. Он не зависит от языка (насколько мне известно) и хорошо работает в большинстве (если не во всех) объектно-ориентированных языках. В руководстве даже говорится:

Интерфейсы определяются так же, как и класс, но с ключевым словом interface, заменяющим ключевое слово class, и без каких-либо методов, у которых определено их содержимое.

Все методы, объявленные в интерфейсе, должны быть открытыми; это природа интерфейса.

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

  1. Мы определяем интерфейс так же, как мы делаем класс, но мы используем ключевое слово interface .
  2. Методы, которые определены в интерфейсе, являются общедоступными (в отличие от protected или private ), потому что это то, что гарантирует функциональность, к которой могут обращаться другие классы.

Прежде чем мы продолжим, как может выглядеть интерфейс в проекте WordPress? Вот пример из проекта, над которым я работал:

01
02
03
04
05
06
07
08
09
10
11
12
<?php
 
/**
 * Defines a common set of functions that any class responsible for loading
 * stylesheets, JavaScript, or other assets should implement.
 */
interface Assets_Interface {
 
    public function init();
    public function enqueue();
 
}

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

Как все мы знаем, WordPress может регистрировать и ставить в очередь два типа ресурсов: таблицы стилей и файлы JavaScript.

Поскольку оба они являются активами, то вполне логично, что когда мы создаем классы для управления таблицами стилей или управления JavaScript, мы обобщаем его как интерфейс ресурсов, верно?

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

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

Вот очень простой пример класса, который добавляет таблицы стилей в область администрирования WordPress:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
 
class CSS_Loader implements Assets_Interface {
 
    /**
     * Registers the ‘enqueue’ function with the proper WordPress hook for
     * registering stylesheets.
     */
    public function init() {
 
        add_action(
            ‘admin_enqueue_scripts’,
            array( $this, ‘enqueue’ )
        );
 
    }
 
    /**
     * Defines the functionality responsible for loading the file.
     */
    public function enqueue() {
 
        wp_enqueue_style(
            ‘tutsplus-namespace-demo’,
            plugins_url( ‘assets/css/admin.css’, dirname( __FILE__ ) ),
            array(),
            filemtime( plugin_dir_path( dirname( __FILE__ ) ) . ‘assets/css/admin.css’ )
        );
 
    }
}

Теперь то, как это реализовано и реализовано с помощью PHP, выходит за рамки данного руководства. Мы многое увидим, когда начнем рефакторинг нашего автозагрузчика.

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

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

Класс (или функция или рутина) должен делать одно и только одно.

Но это немного ошибочно, не так ли? Я имею в виду, что даже простой цикл for выполняет больше чем одно: он инициализирует значение, сравнивает его со значениями, а затем выполняет итерацию значения, когда тело цикла завершено.

Вместо этого принцип гласит следующее:

У класса должна быть только одна причина для изменения.

Поскольку многие из нас, разработчиков, используют Google, чтобы помочь нам в нашей повседневной работе, я думаю, что важно понять источник этой идеи. То есть, это было от дяди Боба Мартина , как он небрежно известен, или от Роберта Мартина, который является автором ряда книг по программированию .

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

Давайте рассмотрим код (и я знаю, что это не класс, это функция, но принцип применим):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
/**
 * Dynamically loads the class attempting to be instantiated elsewhere in the
 * plugin.
 *
 * @package Tutsplus_Namespace_Demo\Inc
 */
 
spl_autoload_register( ‘tutsplus_namespace_demo_autoload’ );
 
/**
 * Dynamically loads the class attempting to be instantiated elsewhere in the
 * plugin by looking at the $class_name parameter being passed as an argument.
 *
 * The argument should be in the form: TutsPlus_Namespace_Demo\Namespace.
 * function will then break the fully-qualified class name into its pieces and
 * will then build a file to the path based on the namespace.
 *
 * The namespaces in this plugin map to the paths in the directory structure.
 *
 * @param string $class_name The fully-qualified name of the class to load.
 */
function tutsplus_namespace_demo_autoload( $class_name ) {
 
    // If the specified $class_name does not include our namespace, duck out.
    if ( false === strpos( $class_name, ‘Tutsplus_Namespace_Demo’ ) ) {
        return;
    }
 
    // Split the class name into an array to read the namespace and class.
    $file_parts = explode( ‘\\’, $class_name );
 
    // Do a reverse loop through $file_parts to build the path to the file.
    $namespace = »;
    for ( $i = count( $file_parts ) — 1; $i > 0; $i— ) {
 
        // Read the current component of the file part.
        $current = strtolower( $file_parts[ $i ] );
        $current = str_ireplace( ‘_’, ‘-‘, $current );
 
        // If we’re at the first entry, then we’re at the filename.
        if ( count( $file_parts ) — 1 === $i ) {
 
            /* If ‘interface’ is contained in the parts of the file name, then
             * define the $file_name differently so that it’s properly loaded.
             * Otherwise, just set the $file_name equal to that of the class
             * filename structure.
             */
            if ( strpos( strtolower( $file_parts[ count( $file_parts ) — 1 ] ), ‘interface’ ) ) {
 
                // Grab the name of the interface from its qualified name.
                $interface_name = explode( ‘_’, $file_parts[ count( $file_parts ) — 1 ] );
                $interface_name = $interface_name[0];
 
                $file_name = «interface-$interface_name.php»;
 
            } else {
                $file_name = «class-$current.php»;
            }
        } else {
            $namespace = ‘/’ .
        }
    }
 
    // Now build a path to the file using mapping to the file location.
    $filepath = trailingslashit( dirname( dirname( __FILE__ ) ) . $namespace );
    $filepath .= $file_name;
 
    // If the file exists in the specified path, then include it.
    if ( file_exists( $filepath ) ) {
        include_once( $filepath );
    } else {
        wp_die(
            esc_html( «The file attempting to be loaded at $filepath does not exist.» )
        );
    }
}

В этой функции происходит много вещей. Просто глядя на это с высокого уровня, мы видим, что он делает следующее:

  • Он определяет, пытается ли PHP вызвать код в этой функции.
  • Функция определяет, загружаем ли мы интерфейс или класс.
  • Затем автозагрузчик пытается включить файл или выдает ошибку.

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

Я не один, чтобы уклоняться от комментариев к коду, но в приведенном выше коде есть много объяснений. И хорошо, когда вы только начинаете писать автозагрузчик, но когда вы движетесь на более продвинутую территорию, такую ​​как мы, тогда это не выдержит более строгих архитектур.

Здесь интерфейсы и принцип единой ответственности могут работать рука об руку.

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

Но возникает интересный вопрос: должны ли мы иметь несколько интерфейсов? И ответ заключается в том, что это зависит от характера решения, которое вы пытаетесь создать.

В нашем случае, я думаю, это имеет смысл.

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

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

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

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

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

Как всегда, если вы ищете другие утилиты, которые помогут вам создать свой растущий набор инструментов для WordPress или, например, код для изучения и стать более опытным в WordPress, не забудьте посмотреть, что у нас есть в Envato Market. ,

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