SHORTINIT в WordPress: минимальная загрузка для работы с базой данных

# Константа SHORTINIT: WordPress с минимальной загрузкой

У меня возник вопрос: как использовать объект $wpdb и базу данных сайта, с которой я работаю, при минимальной загрузке окружения WordPress? Это может понадобиться, например, когда я использую Ajax для получения или записи данных в базу, но ничего более:

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

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

![Оптимизация WordPress](assets/uploads/2021/09/clipboard-image-355045.png)

## Как решить эту задачу

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

С версии 3.4 разработчики WordPress добавили константу SHORTINIT в файл wp-settings.php:

```php
// Остановить основную загрузку WordPress, если нам нужна только основа.
if ( SHORTINIT )
    return false;

Это работает так:

// Указываем, что нам нужно минимум от WP
define('SHORTINIT', true);

// Загружаем окружение WordPress
// WP выполняет некоторые проверки и загружает только самое необходимое для подключения к базе данных
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );

// Здесь мы можем общаться с базой данных.
// Но практически никакие функции WP не будут работать.
// Глобальные переменные $wp, $wp_query, $wp_the_query не установлены.
global $wpdb;
$result = $wpdb->get_results(
    "SELECT post_title FROM $wpdb->posts WHERE post_type='post'"
);

if( $result ){
    foreach( $result as $post ){
        echo "$post->post_title 
"; } }

Конкретные цифры

Чтобы увидеть, как инициализации с SHORTINIT и без него отличаются, я измерил: количество SQL-запросов, время выполнения кода и потребление памяти. Вот результаты:

require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );
// 5 SQL запросов за 0.1 сек. Память: 14.92 MB

define( 'SHORTINIT', true );
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );
// 0 SQL запросов за 0.02 сек. Память: 2.35 MB

Таким образом, SHORTINIT уменьшает нагрузку по всем параметрам примерно в 5 раз. Неплохо!

Что работает с SHORTINIT

При использовании SHORTINIT система фильтров apply_filters() и do_action() уже работает. Однако базовые фильтры (/wp-includes/default-filters.php) работают, а темы и плагины, которые вы указали в functions.php, не будут работать.

Функции, такие как esc_attr(), is_single(), the_content(), get_permalink() и другие, не будут работать. Вот все функции, которые подключены - смотрите в wp-settings.php:

  • /wp-includes/version.php
  • /wp-includes/compat.php
  • /wp-includes/load.php

Подключаемые файлы

Подключаются основные файлы для инициализации:

  • /wp-includes/class-wp-paused-extensions-storage.php
  • /wp-includes/class-wp-fatal-error-handler.php
  • /wp-includes/class-wp-recovery-mode-cookie-service.php
  • /wp-includes/class-wp-recovery-mode-key-service.php
  • /wp-includes/class-wp-recovery-mode-link-service.php
  • /wp-includes/class-wp-recovery-mode-email-service.php
  • /wp-includes/class-wp-recovery-mode.php
  • /wp-includes/error-protection.php
  • /wp-includes/default-constants.php
  • /wp-includes/plugin.php

Если включена константа WP_CACHE:

  • /wp-content/advanced-cache.php

Загружаются ранние файлы WordPress:

  • /wp-includes/class-wp-list-util.php
  • /wp-includes/formatting.php
  • /wp-includes/meta.php
  • /wp-includes/functions.php
  • /wp-includes/class-wp-meta-query.php
  • /wp-includes/class-wp-matchesmapregex.php
  • /wp-includes/class-wp.php
  • /wp-includes/class-wp-error.php
  • /wp-includes/pomo/mo.php
  • /wp-includes/default-filters.php

Для мультисайтов загружаются:

  • /wp-includes/class-wp-site-query.php
  • /wp-includes/class-wp-network-query.php
  • /wp-includes/ms-blogs.php
  • /wp-includes/ms-settings.php

Авторизация с SHORTINIT

Пример проверки авторизации и получения всех прав пользователя с использованием SHORTINIT:

get_results( "
        SELECT option_name, option_value FROM $wpdb->options 
        WHERE option_name IN ('siteurl', '{$wpdb->prefix}user_roles')
    ", 'OBJECT_K' );

    if( ! $_options ){
        return [ 'error', 'Опции не найдены' ];
    }

    $_c_hash = md5( $_options['siteurl']->option_value );
    if( ! isset( $_COOKIE[ "wordpress_logged_in_$_c_hash" ] ) ){
        return [ 'error', 'Нет cookies.' ];
    }

    $cookie = $_COOKIE[ "wordpress_logged_in_$_c_hash" ];
    $cookie_elements = explode( '|', $cookie );

    if( count( $cookie_elements ) !== 4 ){
        return [ 'error', 'Cookie повреждена' ];
    }

    $username = $cookie_elements[0];
    $expiration = $cookie_elements[1];
    $token = $cookie_elements[2];
    $hmac = $cookie_elements[3];

    if( $expiration < time() ){
        return [ 'error', 'Время сессии истекло' ];
    }

    $user = $wpdb->get_row( $wpdb->prepare(
        "SELECT * FROM $wpdb->users WHERE user_login=%s", $username ), 'OBJECT'
    );

    if( ! $user ){
        return [ 'error', 'Пользователя не существует' ];
    }

    $pass_frag = substr( $user->user_pass, 8, 4 );
    $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token );
    $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
    $hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key );
    if( ! hash_equals( $hash, $hmac ) ){
        return [ 'error', 'Хеш не совпадает' ];
    }

    $user_options = $wpdb->get_results( "
        SELECT meta_key, meta_value FROM $wpdb->usermeta 
        WHERE user_id = $user->ID AND meta_key IN ( 'session_tokens', '{$wpdb->prefix}capabilities' )
    ", OBJECT_K );

    if( ! $user_options ){
        return [ 'error', 'Опции пользователя не установлены' ];
    }

    $sessions = unserialize( $user_options['session_tokens']->meta_value );
    $verifier = hash_token( $token );

    if( ! isset( $sessions[ $verifier ] ) ){
        return [ 'error', 'Нет токена авторизации' ];
    }

    if( $sessions[ $verifier ]['expiration'] < time() ){
        return [ 'error', 'Время сессии истекло' ];
    }

    $role_caps = unserialize( $_options[ $wpdb->prefix . 'user_roles' ]->option_value );
    $user_caps = unserialize( $user_options[ $wpdb->prefix . 'capabilities' ]->meta_value );
    $all_caps = [];

    foreach( $user_caps as $key => $value ){
        if( isset( $role_caps[ $key ] ) && $value ){
            $all_caps = array_merge( $all_caps, $role_caps[ $key ]['capabilities'], true );
        }
        else{
            $all_caps[ $key ] = $value;
        }
    }

    return [ 'success', $all_caps ];
}

Теперь у вас есть более простое и понятное объяснение использования константы SHORTINIT в WordPress. Вы можете легко адаптировать этот код и использовать его в своих проектах.

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *