Статьи

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

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

В частности, мы затронули следующие темы:

  • объектно-ориентированные интерфейсы
  • принцип единой ответственности
  • как они выглядят в PHP
  • куда мы направляемся с нашим плагином

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

С учетом сказанного, я предполагаю, что вы все застали врасплох.

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

Итак, в этом уроке план следующий:

  1. Изучите код, который мы написали до сих пор.
  2. Определите, как мы можем реорганизовать его, используя объектно-ориентированные методы.
  3. Предоставьте план высокого уровня для нашей реализации.

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

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

  • локальная среда разработки, подходящая для вашей операционной системы
  • каталог, из которого размещается WordPress 4.6.1
  • текстовый редактор или IDE
  • знание 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
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
<?php
 
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 следующим образом:

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

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

Код делает следующее:

  1. Проверяет, что мы работаем явно с нашим пространством имен.
  2. Разбивает имя входящего класса на части, чтобы определить, является ли он классом или интерфейсом (поэтому $class_name является плохим именем переменной).
  3. Проверяет, работаем ли мы с файлом интерфейса.
  4. Проверяет, работаем ли мы с файлом классов.
  5. Проверяет, работаем ли мы с интерфейсом.
  6. Основываясь на результатах вышеупомянутых условий, генерирует имя файла.
  7. Создает путь к файлу на основе созданного имени файла.
  8. Если файл существует в сгенерированном имени, включает его.
  9. В противном случае код генерирует ошибку.

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

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

И это поднимает вопрос: где мы вообще начинаем?

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

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

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

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

Учитывая то, что я перечислил выше, я придумал следующие классы:

  1. Автозагрузчик . Это основной класс, который в конечном итоге отвечает за включение нашего класса, нашего пространства имен или нашего интерфейса. Мы назовем этот класс. Остальные — это классы, которые позаботятся о необходимой работе, которую этот класс должен включить в файл.
  2. NamespaceValidator . Этот файл будет смотреть на входящий класс, интерфейс или что у вас есть, и определит, является ли он действительным. Это даст нам решающий фактор, если мы сможем перейти к остальной части нашего кода, а не к.
  3. FileInvestigator . Этот класс смотрит на тип файла, который передается в автозагрузчик. Он определит, является ли он классом, интерфейсом или пространством имен, и вернет полное имя пути к файлу, чтобы его можно было включить.
  4. FileRegistry . При этом будет использован полный путь к файлу, который в конечном итоге возвращается из других классов, и он будет включен в плагин.

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

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

На этом этапе разные разработчики, фирмы, агентства и команды будут по-разному подходить к разработке системы, в которой они работают.

Один из наиболее распространенных способов сделать это — использовать диаграмму UML. Хотя это полезно, это не то, что стоит делать в рамках этого учебника, потому что для объяснения всех частей потребуется целый другой учебник.

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

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

Давайте начнем с Autoloader и будем работать оттуда.

Помните, этот класс отвечает за включение необходимого файла. Это файл, который будет зарегистрирован функцией spl_autoload_register .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<?php
 
class Autoloader {
 
        private $namespace_validator;
 
    private $file_registry;
 
    public function __construct() {
 
        $this->namespace_validator = new NamespaceValidator();
        $this->file_registry = new FileRegistry();
    }
 
    public function load( $filename ) {
 
        if ( $this->namespace_validator->is_valid( $filename ) ) {
            $this->file_registry->load( $filename );
        }
    }
}

Обратите внимание, что этот класс зависит от NamespaceValidator и класса FileRegistry . Мы рассмотрим каждый из них более подробно в одно мгновение.

Этот файл будет смотреть на входящее имя файла и определит, будет ли оно действительным. Это делается путем просмотра пространства имен в имени файла.

1
2
3
4
5
6
7
8
<?php
 
class NamespaceValidator {
 
    public function is_valid( $filename ) {
        return ( 0 === strpos( $filename, ‘Tutsplus_Namespace_Demo’ ) );
    }
}

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

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

Затем он получает полное имя файла для типа файла.

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
<?php
 
class FileInvestigator {
 
    public function get_filetype( $filename ) {
 
            $filepath = »;
            for ( $i = 1; $i < count( $file_parts ); $i++ ) {
 
                $current = strtolower( $file_parts[ $i ] );
                $current = str_ireplace( ‘_’, ‘-‘, $current );
 
                $filepath = $this->get_file_name( $file_parts, $current, $i );
                if ( count( $file_parts ) — 1 !== $i ) {
                    $filepath = trailingslashit( $filepath );
                }
            }
         
            return $filepath;
    }
 
    private function get_file_name( $file_parts, $current, $i ) {
 
        $filename = »;
 
        if ( count( $file_parts ) — 1 === $i ) {
 
            if ( $this->is_interface( $file_parts ) ) {
                $filename = $this->get_interface_name( $file_parts );
            } else {
                $filename = $this->get_class_name( $current );
            }
        } else {
            $filename = $this->get_namespace_name( $current );
        }
 
        return $filename;
    }
 
    private function is_interface( $file_parts ) {
        return strpos( strtolower( $file_parts[ count( $file_parts ) — 1 ] ), ‘interface’ );
    }
 
    private function get_interface_name( $file_parts ) {
 
        $interface_name = explode( ‘_’, $file_parts[ count( $file_parts ) — 1 ] );
        $interface_name = $interface_name[0];
 
        return «interface-$interface_name.php»;
    }
 
    private function get_class_name( $current ) {
        return «class-$current.php»;
    }
 
    private function get_namespace_name( $current ) {
        return ‘/’ .
    }
}

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

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

Это будет использовать полный путь к файлу и включать файл; в противном случае он будет использовать WordPress API для отображения сообщения об ошибке.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class FileRegistry {
 
    private $investigator;
 
    public function __construct() {
        $this->investigator = new FileInvestigator();
    }
 
    public function load( $filepath ) {
 
        $filepath = $this->investigator->get_filetype( $filepath );
        $filepath = rtrim( plugin_dir_path( dirname( __FILE__ ) ), ‘/’ ) .
 
        if ( file_exists( $filepath ) ) {
            include_once( $filepath );
        } else {
 
            wp_die(
                esc_html( ‘The specified file does not exist.’ )
            );
        }
    }
}

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

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

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

Является ли решение, над которым мы работаем, более приемлемым, чем то, что у нас есть? Абсолютно. Будет ли это работать в контексте WordPress и нашего существующего плагина? Мы не узнаем, пока не начнем подключать это к нашему плагину.

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

Как бы то ни было, код, который у нас сейчас есть, должен быть более читабельным (хотя у нас еще есть DocBlocks и некоторые встроенные комментарии для представления) и более легким в обслуживании и даже более тестируемым.

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

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

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