Как создать гибкую абстракцию для WP_Query?

8

Мой вопрос о PHP, но он включает в себя WordPress, как я создаю плагин. Дело в том, что у меня есть 5 вопросов, у каждого вопроса есть 6 вариантов и один вариант на выбор. Теперь человек будет выбирать любой выбор из каждого или нескольких. Я создал условие if, которое сейчас сводит меня с ума, так как оно прошло слишком долго и будет продвигаться дальше, например, будет сделано почти 100 комбинаций. Я бы не хотел этого, я знаю, что есть способ многомерного массива, но я не плагин или эксперт PHP для WordPress. так что если кто-нибудь может разобрать это для меня.

$qs = $_POST['q1'];
$q2 = $_POST['q2'];
$q3 = $_POST['q3'];
$q4 = $_POST['q4'];
$q5 = $_POST['q5'];
$q6 = $_POST['q6'];



 $args = array(
  'post_type' => 'product',
  'posts_per_page' => -1,
  'tax_query' => array(
    'relation' => 'AND',
    array(
     'taxonomy' => 'product_cat',
     'field' => 'slug',
     'terms' => 'fashion-follower'
    ),
//    array(
//     'taxonomy' => 'product_cat',
//     'field' => 'slug',
//     'terms' => 'cheap-and-cheerful'
//    )
  )
);
 //The Fashionsia
 if (($qs ==='party') && ($q2 === 'clothes') && ($q3 === 'shopping') && ($q5 === 'Sunbathing') && ($q6 === 'mini')){

$query = new WP_Query( $args );
if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

     the_post_thumbnail('thumbnail');

endwhile;
    endif;      
}

//second question loop

$args2 = array(
  'post_type' => 'product',
  'posts_per_page' => -1,
  'tax_query' => array(
    'relation' => 'AND',
    array(
     'taxonomy' => 'product_cat',
     'field' => 'slug',
     'terms' => 'the-homemaker'
    ),
//    array(
//     'taxonomy' => 'product_cat',
//     'field' => 'slug',
//     'terms' => 'cheap-and-cheerful'
//    )
  )
);
 //The homemaker
 if (($qs ==='drink') && ($q2 === 'candles') && ($q3 === 'house') && ($q4 === 'diy')){

$query = new WP_Query( $args2 );
if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

     the_post_thumbnail('thumbnail');

endwhile;
    endif;      
}
//third loop

$args3 = array(
  'post_type' => 'product',
  'posts_per_page' => -1,
  'tax_query' => array(
    'relation' => 'AND',
    array(
     'taxonomy' => 'product_cat',
     'field' => 'slug',
     'terms' => 'entertainment'
    ),
//    array(
//     'taxonomy' => 'product_cat',
//     'field' => 'slug',
//     'terms' => 'cheap-and-cheerful'
//    )
  )
);
 //The Entertainer
 if (($qs ==='party-babe') && ($q2 === 'winer')&& ($q4 === 'storm') && ($q6 === 'limo')){

$query = new WP_Query( $args3 );
if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

     the_post_thumbnail('thumbnail');

endwhile;
    endif;      
}
//fourth loop
$args4 = array(
  'post_type' => 'product',
  'posts_per_page' => -1,
  'tax_query' => array(
    'relation' => 'AND',
    array(
     'taxonomy' => 'product_cat',
     'field' => 'slug',
     'terms' => 'family-fanatic'
    ),
//    array(
//     'taxonomy' => 'product_cat',
//     'field' => 'slug',
//     'terms' => 'cheap-and-cheerful'
//    )
  )
);
 //The family-fanatic
 if (($qs ==='movie') && ($q2 === 'kids')&& ($q6 === 'volvo')){

$query = new WP_Query( $args4 );
if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

     the_post_thumbnail('thumbnail');

endwhile;
    endif;      
}
//fifth loop
//fourth loop
$args4 = array(
  'post_type' => 'product',
  'posts_per_page' => -1,
  'tax_query' => array(
    'relation' => 'AND',
    array(
     'taxonomy' => 'product_cat',
     'field' => 'slug',
     'terms' => 'family-fanatic'
    ),
//    array(
//     'taxonomy' => 'product_cat',
//     'field' => 'slug',
//     'terms' => 'cheap-and-cheerful'
//    )
  )
);
 //The romantic
 if (($qs ==='Dinner-show') && ($q5 === 'cruiser')){

$query = new WP_Query( $args4 );
if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

     the_post_thumbnail('thumbnail');

endwhile;
    endif;      
}
Нофел
источник
В чем разница с вашим предыдущим вопросом ?
fuxia
@toscho разница в том, что в том Q. был вопрос о рефакторинге, но теперь я спрашиваю об оптимизации кода вместе с циклом wp или о том, чтобы сделать это в массиве.
Нофель

Ответы:

18

Ваш вопрос на самом деле не о WordPress, а о PHP и рефакторинге. Но мы видим здесь очень много плохого кода, и шаблон, который я объясню ниже (MVC), может помочь многим другим разработчикам, поэтому я решил написать небольшой ответ. Имейте в виду, что в нашей сети есть специальный сайт для таких вопросов: Code Review . К сожалению, очень немногие разработчики WordPress работают там.


Как изменить код

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

1. Удалите бесполезный код. Украсить остальное.

Выход

У вас есть этот повторяющийся фрагмент:

if( $query->have_posts()) : while( $query->have_posts() ) : $query->the_post();

the_post_thumbnail('thumbnail');

endwhile;
endif;

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

echo get_the_post_thumbnail( $post_id, 'post-thumbnail' );

Запрос

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

Простой пример:

$post_ids = array();
$args     = array(
    'post_type'      => 'post',
    'posts_per_page' => 10,
    'fields'         => 'ids'
);
$query    = new WP_Query( $args );

if ( ! empty ( $query->posts ) )
    $post_ids = $query->posts; // just the post IDs

Теперь у вас есть идентификаторы, и вы можете написать:

foreach ( $post_ids as $post_id )
    echo get_the_post_thumbnail( $post_id, 'post-thumbnail' );

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

Синтаксис

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

Это также причина, почему я удалил endwhileи endif. Альтернативный синтаксис является грязным и трудно читать. Кроме того, это значительно усложняет работу в среде IDE : с фигурными скобками легче складывать и прыгать от начала до конца выражения.

Значения по умолчанию

В вашем $argsмассиве есть поля, которые вы используете везде. Создайте массив по умолчанию и запишите эти поля только один раз :

$args = array(
    'post_type'      => 'product',
    'posts_per_page' => 100,
    'fields'         => 'ids',
    'tax_query'      => array(
        array(
            'taxonomy' => 'product_cat',
            'field'    => 'slug',
        )
    )
);

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

Теперь все, что вам нужно изменить, это поле $args[ 'tax_query' ][ 'terms' ]. Мы расскажем об этом чуть позже.

2. Найдите все повторяющиеся выражения и создайте процедуры

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

Отделите эти группы от остальных, создайте массив, которым вы можете управлять позже отдельно:

$groups = array(
    'fashion-follower' => array(
        'q1' => 'party',
        'q2' => 'clothes',
        'q3' => 'shopping',
        'q4' => FALSE,
        'q5' => 'sunbathing',
        'q6' => 'mini',
    ),
    'the-homemaker' => array(
        'q1' => 'drink',
        'q2' => 'candles',
        'q3' => 'house',
        'q4' => 'diy',
        'q5' => FALSE,
        'q6' => FALSE,
    )
);

Чтобы заполнить пропущенное termsполе в вашем массиве по умолчанию, вы пробегаете $groupsмассив, пока не найдете соответствие:

function get_query_term( $groups )
{
    foreach ( $groups as $term => $values )
    {
        if ( compare_group_values( $values ) )
            return $term;
    }

    return FALSE;
}

function compare_group_values( $values )
{
    foreach ( $values as $key => $value )
    {
        // Key not sent, but required
        if ( empty ( $_POST[ $key ] ) and ! empty ( $value ) )
            return FALSE;

        // Key sent, but wrong value
        if ( ! empty ( $_POST[ $key ] ) and $_POST[ $key ] !== $value )
            return FALSE;
    }

    // all keys matched the required values
    return TRUE;
}

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

Теперь у нас есть все части, давайте склеим их.

3. Организация: отделить модель от вида

Когда я писал модель и представление , я имел в виду кое-что: подход MVC. Это расшифровывается как Model View Controller , хорошо известный шаблон для организации программных компонентов. Пока отсутствующей частью был контроллер, позже мы увидим, как мы его используем.

Вы сказали, что вы мало знаете о PHP, поэтому я надеюсь, что вы знаете больше о выводе. :) Начнем с этого:

class Thumbnail_List
{
    protected $source;

    public function set_source( Post_Collector_Interface $source )
    {
        $this->source = $source;
    }

    public function render()
    {
        $post_ids = $this->source->get_post_ids();

        if ( empty ( $post_ids ) or ! is_array( $post_ids ) )
            return print 'Nothing found';

        foreach ( $post_ids as $post_id )
            echo get_the_post_thumbnail( $post_id, 'post-thumbnail' );
    }
}

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

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

Теперь источник для нашего взгляда, модель.

class Post_Collector implements Post_Collector_Interface
{
    protected $groups = array();

    public function set_groups( Array $groups )
    {
        $this->groups = $groups;
    }

    public function get_post_ids()
    {
        $term = $this->get_query_term();

        if ( ! $term )
            return array();

        return $this->query( $term );
    }

    protected function query( $term )
    {
        $args = array(
            'post_type'      => 'product',
            'posts_per_page' => 100,
            'fields'         => 'ids',
            'tax_query'      => array(
                array(
                    'taxonomy' => 'product_cat',
                    'field'    => 'slug',
                    'terms'    => $term
                )
            )
        );

        $query = new WP_Query( $args );

        if ( empty ( $query->posts ) )
            return array();

        return $query->posts;
    }

    protected function get_query_term()
    {
        foreach ( $this->groups as $term => $values )
        {
            if ( compare_group_values( $values ) )
                return $term;
        }

        return FALSE;
    }

    protected function compare_group_values( $values )
    {
        foreach ( $values as $key => $value )
        {
            // Key not sent, but required
            if ( empty ( $_POST[ $key ] ) and ! empty ( $value ) )
                return FALSE;

            // Kent sent, but wrong value
            if ( ! empty ( $_POST[ $key ] ) and $_POST[ $key ] !== $value )
                return FALSE;
        }

        // all keys matched the required values
        return TRUE;
    }
}

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

Эти publicметоды просты: первый получает наш $groupмассив сверху, второй возвращает массив почтовых идентификаторов. И снова мы встречаемся с этим сомнительным Post_Collector_Interface.

Интерфейс представляет собой договор . Это может быть подписано (реализовано) классами. Требование интерфейса, как Thumbnail_Listделает наш класс , означает: класс ожидает некоторый другой класс с этими открытыми методами.

Давайте создадим этот интерфейс. Это действительно просто:

interface Post_Collector_Interface
{
    public function set_groups( Array $groups );

    public function get_post_ids();
}

Да, это все. Простой код, не правда ли?

То, что мы сделали здесь: мы сделали нашу точку зрения Thumbnail_Listнезависимой от конкретного класса, в то время как мы все еще можем полагаться на методы класса, который мы получили $source. Если позже вы передумаете, вы можете написать новый класс для извлечения идентификаторов сообщений или использовать класс с фиксированными значениями. Пока вы реализуете интерфейс, представление будет удовлетворено. Теперь вы можете даже протестировать вид с помощью фиктивного объекта:

class Mock_Post_Collector implements Post_Collector_Interface
{
    public function set_groups( Array $groups ) {}

    public function get_post_ids()
    {
        return array ( 1 );
    }
}

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

Теперь мы должны как-то совместить наши занятия. Здесь контроллер выходит на сцену.

class Thumbnail_Controller
{
    protected $groups = array(
        'fashion-follower' => array(
            'q1' => 'party',
            'q2' => 'clothes',
            'q3' => 'shopping',
            'q4' => FALSE,
            'q5' => 'sunbathing',
            'q6' => 'mini',
        ),
        'the-homemaker' => array(
            'q1' => 'drink',
            'q2' => 'candles',
            'q3' => 'house',
            'q4' => 'diy',
            'q5' => FALSE,
            'q6' => FALSE,
        )
    );
    public function __construct()
    {
        // not a post request
        if ( 'POST' !== $_SERVER[ 'REQUEST_METHOD' ] )
            return;

        // set up the model
        $model = new Post_Collector;
        $model->set_groups( $this->groups );

        // prepare the view
        $view = new Thumbnail_List;
        $view->set_source( $model );

        // finally render the tumbnails
        $view->render();
    }
}

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

И теперь вам нужно сделать только одно:

// Let the dogs out!
new Thumbnail_Controller;

Называйте эту линию там, где вам нужен выход.

Вы можете найти весь код из этого ответа в GistHub .

Фуксия
источник
6
Какой номер ISBN этого ответа?
Кайзер
Ответ учебника действительно: р
Мэнни Флермонд
1
"Никогда не спрашивай -1." Я бы сказал: установите фильтр, -1чтобы пользователи с огромными сайтами могли изменить его, если это необходимо.
chrisguitarguy
@chrisguitarguy Я бы предпочел установить безопасные значения по умолчанию и позволить пользователям, которым нужно, -1добавить это для каждого фильтра. Что уже возможно с фильтром по запросу.
fuxia