Публикация в группе: Плагин WP-Recall - Личный кабинет на WordPress

Категории группы: Полезное

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

  • добавление данных,
  • их обновление или изменение,
  • удаление,
  • а также их получение.

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

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

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

Шестнадцатая версия плагина WP-Recall, кроме всего прочего, получила в ядро новый класс Rcl_Query, который позволил перевести почти все запросы к БД на получение данных  на себя, сформировав, таким образом, рекомендуемый стандарт, которым очень удобно пользоваться. Рассмотрим подробнее порядок работы с новым классом и прикинем его возможности для применения в наших будущих разработках к плагину WP-Recall.

Внимание! Для того, чтобы читать и понимать суть написанного ниже, вы должны иметь представление о работе с классом wpdb и уметь строить SQL-запросы к базе данных.

Для начала рассмотрим базовый пример работы с данным классом напрямую, а затем перейдем к более удобному варианту.

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

$table = RCL_PREF ."groups";
$sql = "CREATE TABLE IF NOT EXISTS ". $table . " (
        ID INT(20) NOT NULL,
        admin_id INT(20) NOT NULL,
        group_users INT(20) NOT NULL,
        group_status VARCHAR(20) NOT NULL,
        group_date DATETIME NOT NULL,
        PRIMARY KEY id (id),
        KEY admin_id (admin_id)
      ) $collate;";

Чтобы начать получать данные из этой таблицы через класс Rcl_Query необходимо сообщить классу структуру этой таблицы. Делается это следующим образом:

$table = array(
	'name' => RCL_PREF ."groups",
	'as' => 'rcl_groups',
	'cols' => array(
		'ID',
		'admin_id',
		'group_users',
		'group_status',
		'group_date'
	)
);

$query = new Rcl_Query($table);

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

Мы помним, что для построения запросов к БД через класс $wpdb мы могли использовать методы: get_var, get_row, get_col и get_results. При работе с классом Rcl_Query эти методы также можно и нужно использовать, только передавать в них мы будем не строку запроса, а массив.

Итак давайте рассмотрим несколько простых примеров.

... WHERE 'a' = 'b' ...

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

1. Получим данные группы с идентификатором 10:

$args = array(
    'ID' => 76
);

$query->get_row($args);

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

stdClass Object
(
    [ID] => 76
    [admin_id] => 1
    [group_users] => 105
    [group_status] => closed
    [group_date] => 2015-11-15 18:24:18
)

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

$args = array(
    'admin_id' => 10,
    'group_status' => 'open'
);

$query->get_row($args);

SELECT 'a', 'b' ...

Если необходимо выбрать значения только из определенных столбцов таблицы, то используем их перечисление внутри массива 'fields'.

2. Для того, чтобы получить только значения admin_id и group_status, построим следующий запрос:

    $args = array(
        'ID' => 76,
        'fields' => array(
            'admin_id',
            'group_status'
        )
    );

    $result = $query->get_row($args);

и тогда мы действительно получим объект только с указанными данными:

stdClass Object
(
    [admin_id] => 1
    [group_status] => closed
)

3. Если мы хотим получить значение только одного столбца, то имеет смысл использовать метод get_var:

    $args = array(
        'ID' => 76,
        'fields' => array(
            'admin_id'
        )
    );

    $result = $query->get_var($args);

тогда мы получим только идентификатор администратора группы, в данном случае, "1".

Перейдем к более сложным и интересным запросам с методом get_results.

... WHERE 'a' = 'b'  AND 'c' = 'd' ...

4. Получим данные групп, в которых admin_id равен 1, а group_status - 'closed', т.е. закрытые группы пользователя с ID 1:

    $args = array(
        'admin_id' => 1,
        'group_status' => 'closed'
    );

    $result = $query->get_results($args);

В результате получим вполне ожидаемый массив объектов:

Array
(
    [0] => stdClass Object
        (
            [ID] => 656
            [admin_id] => 1
            [group_users] => 4
            [group_status] => closed
            [group_date] => 2016-04-06 13:40:57
        )

    [1] => stdClass Object
        (
            [ID] => 605
            [admin_id] => 1
            [group_users] => 8
            [group_status] => closed
            [group_date] => 2016-03-09 14:29:23
        )

    [2] => stdClass Object
        (
            [ID] => 76
            [admin_id] => 1
            [group_users] => 105
            [group_status] => closed
            [group_date] => 2015-11-15 18:24:18
        )

)

... WHERE 'a' >= 'b'  AND 'c' <= 'd' ...

5. Чтобы получить данные со значения от указанного или со значениям до указанного включительно, необходимо при указании наименования колонки использовать постфиксы "__from" и "__to". Например, выберем группы в которых количество участников более 100, но менее 200:

    $args = array(
        'group_users__from' => 100, //от 100
        'group_users__to' => 200 //до 200
    );

    $result = $query->get_results($args);

... WHERE 'a' LIKE '%b%' ...

5. Для выборки данных через указание паттерна LIKE необходимо указывать постфикс "__like":

    $args = array(
        'group_name__like' => 'string'
    );

    $result = $query->get_results($args);

... ORDER BY 'a' ORDER ...

Для сортировки по значениям определенного столбца таблицы используем указание параметра 'orderby' указав в качестве значения наименования нужного столбца таблицы.

6. Усложним запрос, получив только значения указанных столбцов и отсортировав по кол-ву пользователей в группе по убыванию:

    $args = array(
        'admin_id' => 1,
        'group_status' => 'closed',
        'fields' => array(
            'ID',
            'group_users',
        ),
        'orderby' => 'group_users',
        'order' => 'DESC'
    );

    $result = $query->get_results($args);

Результат данного запроса:

Array
(
    [0] => stdClass Object
        (
            [ID] => 76
            [group_users] => 105
        )

    [1] => stdClass Object
        (
            [ID] => 605
            [group_users] => 8
        )

    [2] => stdClass Object
        (
            [ID] => 656
            [group_users] => 4
        )

)

... LIMIT a,b ...

Для пропуска определенного количества найденных первых значений из выборки используется параметр 'offset' с указанным значением количества пропускаемых строк. Параметр 'number' указывает максимальное количество найденных строк в выборке.

7. Дополним запрос, указав к выборке только 3 группы, пропустив первые 5:

    $args = array(
        'admin_id' => 1,
        'group_status' => 'closed',
        'fields' => array(
            'ID',
            'group_users',
        ),
        'orderby' => 'group_users',
        'order' => 'DESC',
        'offset' => 5,
        'number' => 3
    );

    $result = $query->get_results($args);

если для параметра number указать значение "-1", то будут выбраны все записи без ограничений

... 'a' IN (b,c) ...

Если необходимо выбрать строки с определенными значениями указанных столбцов, то используется указание параметра с именем построенным по шаблону: 'colname__in', а в качестве значения указывается массив значений столбца к выборке. Например:

'admin_id__in' => array(1,44)
'ID__in' => array(76,35,10)
'group_users__in' => array(100,150,200)

8. Получим все группы, в которых администраторами являются пользователи с ID равным 1 и 44, отсортируем результат по идентификатору группы:

    $args = array(
        'admin_id__in' => array(1,44),
        'fields' => array(
            'ID',
            'admin_id',
            'group_users',
        ),
        'orderby' => 'ID',
        'order' => 'DESC'
    );

    $result = $query->get_results($args);

... 'a' NOT IN (b,c) ...

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

'admin_id__not_in' => array(1,44)
'ID__not_in' => array(76,35,10)
'group_users__not_in' => array(100,150,200)

9. Получим 10 групп, пропустив первые 5 и исключив из выборки группы с идентификаторами 1, 5 и 23:

    $args = array(
        'ID__not_in' => array(1, 5, 23),
        'number' => 10,
        'offset' => 5
    );

    $result = $query->get_results($args);

SELECT COUNT('a') ...

10. Посчитаем все открытые группы сайта:

    $args = array(
        'group_status' => 'open'
    );

    $result = $query->count($args);

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

DATE_QUERY

11. Выберем все открытые группы, созданные 20 апреля 2016 года:

    $args = array(
        'group_status' => 'open',
        'date_query' => array(
            'column' => 'group_date',
            'year' => '2016',
            'month' => '04',
            'day' => '20'
        )
    );

    $result = $query->get_results($args);

12. Выберем все группы, созданные за январь и февраль 2017 года:

   $args = array(
        'date_query' => array(
            'column' => 'group_date',
            'value' => array(
                '2017-01',
                '2017-02'
            ),
            'compare' => 'BETWEEN'
        )
    );

    $result = $query->get_results($args);

13. Выберем группы, созданные с 10 января 2017 года по 25 января 2017 года:

    $args = array(
        'date_query' => array(
            'column' => 'group_date',
            'value' => array(
                '2017-01-10',
                '2017-01-25'
            ),
            'compare' => 'BETWEEN'
        )
    );

    $result = $query->get_results($args);

Регистрация дочернего класса для таблицы

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

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

class Rcl_Groups_Query extends Rcl_Query {
    
    function __construct() { 

        $table = array(
            'name' => RCL_PREF ."groups",
            'as' => 'rcl_groups',
            'cols' => array(
                'ID',
                'admin_id',
                'group_users',
                'group_status',
                'group_date'
            )
        );
        
        parent::__construct($table);
        
    }
    
}

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

    $query = new Rcl_Groups_Query();

    $args = array(
        'admin_id' => 1,
        'group_status' => 'closed'
    );

    $result = $query->get_results($args);

Оборачиваем дочерний класс в функцию

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

function get_groups($args){
    
    $query = new Rcl_Groups_Query();

    return $query->get_results($args);
    
}

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

$groups = get_groups(
    array(
        'admin_id' => 1,
        'group_status' => 'closed',
        'fields' => array(
            'ID',
            'group_users',
        ),
        'orderby' => 'group_users',
        'order' => 'DESC',
        'offset' => 5,
        'number' => 3
    )
);

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

JOIN. Работа с несколькими таблицами

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

Чтобы сформировать запрос ко второй таблице, необходимо дополнить массив запроса массивом 'join_query', где будут размещаться данные запроса ко второй таблице. Данный массив должен содержать некоторые обязательные элементы:

'join_query' => array(
    array(
        'table' => array(), //данные таблицы, обязательно
        'on_*' => 'col_name', //параметры объединения по ON, обязательно,
        'join' => 'INNER', //тип объединения, необязательно
    ),
    array(
        ...
    ),
    ...
);

'table' - массив с данными таблицы, со структурой, которую мы рассматривали выше при регистрации первой таблицы,

'on_*' - параметр запроса ON, дополняется наименованием колонки из первой таблицы, а значением является имя колонки из второй таблицы, например, запись вида: 'on_ID' => 'user_id' для таблиц wp_users и wp_usermeta, будет интерпретироваться как 'ON wp_users.ID=wp_usermeta.user_id',

'join' - может быть LEFT, RIGHT, INNER и тд., по-умолчанию - INNER.

Вторая таблица будет иметь следующую структуру:

     $table = RCL_PREF ."groups_users";
     $sql = "CREATE TABLE IF NOT EXISTS ". $table . " (
        ID bigint (20) NOT NULL AUTO_INCREMENT,
        group_id INT(20) NOT NULL,
        user_id INT(20) NOT NULL,
        user_role VARCHAR(20) NOT NULL,
        status_time INT(20) NOT NULL,
        user_date DATETIME NOT NULL";

Допустим мы уже зарегистрировали для этой таблицы свой query-класс Rcl_Groups_Users_Query, поэтому можем переходить к построению запроса. Дополним пример №4 запросом ко второй таблице и получим данные пользователей выбранных из первой таблицы групп:

//получим объект для выборки групп
$Groups = new Rcl_Groups_Query();

//получим объект для выборки членов групп
$Users = new Rcl_Groups_Users_Query();

$args = array(
    'admin_id' => 1,
    'group_status' => 'closed',
    'join_query' => array(
        array(
            'join' => 'LEFT',
            'table' => $Users->query['table'],
            'on_ID' => 'group_id',
        )
    )
);

$result = $Groups->get_results($args);

Переданный массив сформирует следующий запрос:

SELECT 
    rcl_groups.*,
    rcl_groups_users.* 
FROM wp_rcl_groups AS rcl_groups 
LEFT JOIN wp_rcl_groups_users AS rcl_groups_users 
        ON rcl_groups.ID = rcl_groups_users.group_id 
WHERE 
    rcl_groups.admin_id = '1' 
    AND rcl_groups.group_status = 'closed' 
ORDER BY rcl_groups.ID DESC LIMIT 0,30

Для уточнения запроса, мы также можем дополнять массив 'join_query' параметрами, которые рассматривались в примерах выше. Например, дополним запрос указав для выборки группы куда входит пользователь с ID 44, а выбирать из первой и второй таблицы будем только поля указанные в параметрах 'fields':

$Groups = new Rcl_Groups_Query();
$Users = new Rcl_Groups_Users_Query();

$args = array(
    'admin_id' => 1,
    'group_status' => 'closed',
    'fields' => array(
        'ID',
        'group_status'
    ),
    'join_query' => array(
        array(
            'join' => 'LEFT',
            'table' => $Users->query['table'],
            'on_ID' => 'group_id',
            'user_id' => 44,
            'fields' => array(
                'user_id',
                'user_role'
            )
        )
    )
);

$result = $Groups->get_results($args);

В результате будет сформирован более точный запрос:

    SELECT 
        rcl_groups.ID,
        rcl_groups.group_status,
        rcl_groups_users.user_id,
        rcl_groups_users.user_role 
    FROM wp_rcl_groups AS rcl_groups 
    LEFT JOIN wp_rcl_groups_users AS rcl_groups_users 
        ON rcl_groups.ID = rcl_groups_users.group_id 
    WHERE 
        rcl_groups.admin_id = '1' 
        AND rcl_groups.group_status = 'closed' 
        AND rcl_groups_users.user_id = '44' 
    ORDER BY rcl_groups.ID DESC 
    LIMIT 0,30

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

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

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

Только лишь создав таблицу в базе данных и подключив ее к классу Rcl_Query разработчик сразу получает возможность работать с данными этой таблицы и получать данные по множеству входящих параметров!

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

4

Автор публикации

не в сети 17 часов

Андрей CS

12K
занят
Комментарии: 2729Публикации: 481Регистрация: 30-11--0001Продаж/Покупок: 0/0