Статьи

Создание поддерживаемых мета-боксов WordPress: рефакторинг

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

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

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

Чтобы быть ясным, акт рефакторинга (как определено в Википедии ):

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

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

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

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

Если это так, отлично! Не стесняйтесь делать их на своем собственном экземпляре кодовой базы. С этим сказал, давайте начнем.

Если вы посмотрите на нашего конструктора:

1
<?php public function __construct( $name, $version ) { $this->name = $name;

Обратите внимание, что в настоящее время он делает две вещи:

  1. Инициализация свойств, таких как имя и версия
  2. Регистрация хуков с помощью WordPress

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?php
 
public function __construct( $name, $version ) {
 
    $this->name = $name;
    $this->version = $version;
 
    $this->meta_box = new Authors_Commentary_Meta_Box();
 
}
 
public function initialize_hooks() {
     
    add_action( ‘admin_enqueue_scripts’, array( $this, ‘enqueue_admin_styles’ ) );
    add_action( ‘admin_enqueue_scripts’, array( $this, ‘enqueue_admin_scripts’ ) );
     
}

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

1
2
3
4
5
6
7
8
9
<?php
 
function run_author_commentary() {
     
    $author_commentary = new Author_Commentary_Admin( ‘author-commentary’, ‘1.0.0’ );
    $author_commentary->initialize_hooks();
     
}
run_author_commentary();

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

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

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

Далее, давайте продолжим и сделаем то же самое с class-authors-commentary-meta-box.php . Вместо того, чтобы создавать новую функцию, мы можем просто переименовать конструктор, поскольку конструктор ничего не делает. Это означает, что наш код должен выглядеть так:

1
2
3
4
5
6
7
8
<?php
 
public function __construct() {
 
    add_action( ‘add_meta_boxes’, array( $this, ‘add_meta_box’ ) );
    add_action( ‘save_post’, array( $this, ‘save_post’ ) );
 
}

К этому:

1
2
3
4
5
6
7
8
<?php
 
public function initialize_hooks() {
 
    add_action( ‘add_meta_boxes’, array( $this, ‘add_meta_box’ ) );
    add_action( ‘save_post’, array( $this, ‘save_post’ ) );
 
}

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

01
02
03
04
05
06
07
08
09
10
<?php
 
public function initialize_hooks() {
 
    $this->meta_box->initialize_hooks();
 
    add_action( ‘admin_enqueue_scripts’, array( $this, ‘enqueue_admin_styles’ ) );
    add_action( ‘admin_enqueue_scripts’, array( $this, ‘enqueue_admin_scripts’ ) );
 
}

Опять же, обновите страницу, и ваш плагин все еще должен функционировать точно так, как это было до этого рефакторинга.

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

Давайте посмотрим на код в его нынешнем виде:

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
<?php
     
public function save_post( $post_id ) {
 
    /* If we’re not working with a ‘post’ post type or the user doesn’t have permission to save,
     * then we exit the function.
     */
    if ( ! $this->is_valid_post_type() || ! $this->user_can_save( $post_id, ‘authors_commentary_nonce’, ‘authors_commentary_save’ ) ) {
        return;
    }
 
    // If the ‘Drafts’ textarea has been populated, then we sanitize the information.
    if ( ! empty( $_POST[‘authors-commentary-drafts’] ) ) {
 
        // We’ll remove all white space, HTML tags, and encode the information to be saved
        $drafts = trim( $_POST[‘authors-commentary-drafts’] );
        $drafts = esc_textarea( strip_tags( $drafts ) );
 
        update_post_meta( $post_id, ‘authors-commentary-drafts’, $drafts );
 
    } else {
 
        if ( » !== get_post_meta( $post_id, ‘authors-commentary-drafts’, true ) ) {
            delete_post_meta( $post_id, ‘authors-commentary-drafts’ );
        }
 
    }
 
    // If the ‘Resources’ inputs exist, iterate through them and sanitize them
    if ( ! empty( $_POST[‘authors-commentary-resources’] ) ) {
 
        $resources = $_POST[‘authors-commentary-resources’];
        $sanitized_resources = array();
        foreach ( $resources as $resource ) {
 
            $resource = esc_url( strip_tags( $resource ) );
            if ( ! empty( $resource ) ) {
                $sanitized_resources[] = $resource;
            }
 
        }
 
        update_post_meta( $post_id, ‘authors-commentary-resources’, $sanitized_resources );
 
    } else {
 
        if ( » !== get_post_meta( $post_id, ‘authors-commentary-resources’, true ) ) {
            delete_post_meta( $post_id, ‘authors-commentary-resources’ );
        }
 
    }
 
    // If there are any values saved in the ‘Published’ input, save them
    if ( ! empty( $_POST[‘authors-commentary-comments’] ) ) {
        update_post_meta( $post_id, ‘authors-commentary-comments’, $_POST[‘authors-commentary-comments’] );
    } else {
 
        if ( » !== get_post_meta( $post_id, ‘authors-commentary-comments’, true ) ) {
            delete_post_meta( $post_id, ‘authors-commentary-comments’ );
        }
 
    }
 
}

Помимо того, что метод слишком длинный для начала, мы можем очистить несколько вещей:

  1. Исходное условное выражение, использующее логические операторы OR и Logic OR
  2. Условия, которые проверяют наличие информации в массиве $_POST
  3. Функции очистки, обновления и / или удаления связанных метаданных

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

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

Прямо сейчас код читает:

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

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

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

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

Итак, давайте возьмем функцию is_valid_post_type и переместим ее в функцию user_can_save :

01
02
03
04
05
06
07
08
09
10
11
12
<?php
 
private function user_can_save( $post_id, $nonce_action, $nonce_id ) {
 
    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ $nonce_action ] ) && wp_verify_nonce( $_POST[ $nonce_action ], $nonce_id ) );
 
    // Return true if the user is able to save;
    return !
 
}

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

Мы начали с этого:

1
2
3
4
5
<?php
     
if ( ! $this->is_valid_post_type() || ! $this->user_can_save( $post_id, ‘authors_commentary_nonce’, ‘authors_commentary_save’ ) ) {
    return;
}

И теперь у нас есть это:

1
2
3
4
5
<?php
     
if ( ! $this->user_can_save( $post_id, ‘authors_commentary_nonce’, ‘authors_commentary_save’ ) ) {
    return;
}

Читается намного проще, не так ли?

Далее, прежде чем мы начнем санацию, проверку и сохранение (или удаление) метаданных, мы проверяем коллекцию $_POST чтобы убедиться, что данные действительно существуют.

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

Сначала введите следующую функцию (и обратите внимание, что она принимает параметр):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
 
/**
 * Determines whether or not a value exists in the $_POST collection
 * identified by the specified key.
 *
 * @since 1.0.0
 *
 * @param string $key The key of the value in the $_POST collection.
 * @return bool True if the value exists;
 */
private function value_exists( $key ) {
    return !
}

Затем проведите рефакторинг всех вызовов, которые изначально вызывали ! empty( $_POST[ ... ] ) ! empty( $_POST[ ... ] ) чтобы они воспользовались этой функцией.

Например, вызовы функций должны выглядеть так:

1
2
3
4
5
if ( $this->value_exists( ‘authors-commentary-comments’ ) ) {
    // …
} else {
    // …
}

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

Например, мы видим что-то подобное каждый раз:

1
2
3
4
5
<?php
 
if ( » !== get_post_meta( $post_id, ‘authors-commentary-comments’, true ) ) {
    delete_post_meta( $post_id, ‘authors-commentary-comments’ );
}

Это очевидный шанс изменить код. delete_post_meta давайте создадим новую функцию с именем delete_post_meta и delete_post_meta чтобы она инкапсулировала всю эту информацию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<?php
 
/**
 * Deletes the specified meta data associated with the specified post ID
 * based on the incoming key.
 *
 * @since 1.0.0
 * @access private
 * @param int $post_id The ID of the post containing the meta data
 * @param string $meta_key The ID of the meta data value
 */
private function delete_post_meta( $post_id, $meta_key ) {
     
    if ( » !== get_post_meta( $post_id, $meta_key, true ) ) {
        delete_post_meta( $post_id, ‘$meta_key’ );
    }
     
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
     
// If the ‘Drafts’ textarea has been populated, then we sanitize the information.
if ( $this->value_exists( ‘authirs-commentary-drafts’ ) ) {
 
    // We’ll remove all white space, HTML tags, and encode the information to be saved
    $drafts = trim( $_POST[‘authors-commentary-drafts’] );
    $drafts = esc_textarea( strip_tags( $drafts ) );
 
    update_post_meta( $post_id, ‘authors-commentary-drafts’, $drafts );
 
} else {
    $this->delete_post_meta( $post_id, ‘authors-commentary-drafts’ );
}

На данный момент у нас есть только один аспект этой части кода для рефакторинга.

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

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

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

Если мы не работаем с массивом, тогда мы будем рассматривать входящие данные как текст; в противном случае мы будем рассматривать его как массив:

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
<?php
     
/**
 * Sanitizes the data in the $_POST collection identified by the specified key
 * based on whether or not the data is text or is an array.
 *
 * @since 1.0.0
 * @access private
 * @param string $key The key used to retrieve the data from the $_POST collection.
 * @param bool $is_array Optional.
 * @return array|string The sanitized data.
 */
private function sanitize_data( $key, $is_array = false ) {
 
    $sanitized_data = null;
 
    if ( $is_array ) {
 
        $resources = $_POST[ $key ];
        $sanitized_data = array();
 
        foreach ( $resources as $resource ) {
 
            $resource = esc_url( strip_tags( $resource ) );
            if ( ! empty( $resource ) ) {
                $sanitized_data[] = $resource;
            }
 
        }
 
    } else {
 
        $sanitized_data = »;
        $sanitized_data = trim( $_POST[ $key ] );
        $sanitized_data = esc_textarea( strip_tags( $sanitized_data ) );
 
    }
 
    return $sanitized_data;
 
}

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

01
02
03
04
05
06
07
08
09
10
<?php
 
private function update_post_meta( $post_id, $meta_key, $meta_value ) {
     
    if ( is_array( $_POST[ $meta_key ] ) ) {
        $meta_value = array_filter( $_POST[ $meta_key ] );
    }
 
    update_post_meta( $post_id, $meta_key, $meta_value );
}

Теперь мы можем обновить все условные выражения, которые мы использовали ранее в функции, следующим образом:

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
<?php
     
public function save_post( $post_id ) {
 
    if ( ! $this->user_can_save( $post_id, ‘authors_commentary_nonce’, ‘authors_commentary_save’ ) ) {
        return;
    }
 
    if ( $this->value_exists( ‘authors-commentary-drafts’ ) ) {
 
        $this->update_post_meta(
            $post_id,
            ‘authors-commentary-drafts’,
            $this->sanitize_data( ‘authors-commentary-drafts’ )
        );
 
    } else {
        $this->delete_post_meta( $post_id, ‘authors-commentary-drafts’ );
    }
 
    if ( $this->value_exists( ‘authors-commentary-resources’ ) ) {
 
        $this->update_post_meta(
            $post_id,
            ‘authors-commentary-resources’,
            $this->sanitize_data( ‘authors-commentary-resources’, true )
        );
 
    } else {
        $this->delete_post_meta( $post_id, ‘authors-commentary-resources’ );
    }
 
    if ( $this->value_exists( ‘authors-commentary-comments’ ) ) {
 
        $this->update_post_meta(
            $post_id,
            ‘authors-commentary-comments’,
            $_POST[‘authors-commentary-comments’]
        );
 
    } else {
        $this->delete_post_meta( $post_id, ‘authors-commentary-comments’ );
    }
 
}

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

К настоящему времени мы завершили наш плагин. Мы написали плагин, который представляет мета-поле для предоставления опций авторам, пишущим сообщения в блоге.

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

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

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