Лучшие практики PHP-разработки на WordPress

Содержание скрыть

Оригинал: https://10up.github.io/Engineering-Best-Practices/php/

Полезные советы по улучшению производительности, безопасности и чистоте кода вашего проекта от компании 10Up — ведущих разработчиков тем и плагинов для WordPress. Крайне рекомендуется к прочтению и многократному повторению материала всем начинающим (и не только!) WP разработчикам. Многие вопросы кажуться банальными и очень простыми в реализации, но тем не менее неправильные и не оптимальные решения встречаются на каждом первом проекте, сделанном с помощью WordPress. Эта статья позволит если не решить все проблемы разработки на PHP для WordPress, то как минимум значительно сократить их число.

Производительность

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

Оптимизируем запросы к базе данных

При запросах в базу данных WordPress в большинстве случаев стоит использовать WP_Query. Запросы через WP_Query имеют несколько полезных аргументов, а также выполняют много полезной работы в фоновом режиме, чего не происходит при использовании других методов доступа к базе, например get_posts().

Вот некоторые ключевые моменты:

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

'no_found_rows' => true: используйте там, где пагинация не нужна.

'update_post_meta_cache' => false: убирает обновление кэша метаполей. Полезно, если вам не нужны мета-поля в данном запросе.

'update_post_term_cache' => false: если вам не нужно получать термины таксономии, обновление их кэша лучше отключить.

'fields' => 'ids': вместо целого объекта на каждую запись, можно оптимизировать запрос и получать только ID записей.

Не рекомендуется использовать posts_per_page => -1, так как это потенциальная дыра в производительности. Что если у вас 100 000 записей? Один такой запрос может запросто положить сервер. Старайтесь всегда указывать разумные пределы для каждой ситуации. Например, если вы разрабатываете виджет для вывода произвольных записей в сайдбаре, укажите ограничение в 500 записей. Больше вряд ли когда-либо понадобиться.

<?php // Получим 500 записей. new WP_Query( array( 'posts_per_page' => 500, )); ?>

Не используйте $wpdb или get_posts() без весомой причины. get_posts() на самом деле вызывает WP_Query, но по-умолчанию обходит некоторые фильтры. Уверены, что вам это нужно? Скорее всего, нет.

Если вы не планируете использовать пагинацию результатов запроса, обязательно передайте параметр no_found_rows => true в WP_Query. Это попросит WordPress не включать SQL_CALC_FOUND_ROWS в SQL запрос, значительно увеличивая скорость его выполнения. Команда SQL_CALC_FOUND_ROWS рассчитывает общее количество строк в запросе, необходимое для работы пагинации.

<?php // Отключает подсчет страниц пагинации (SQL_CALC_FOUND_ROWS) для лучшей производительности. new WP_Query( array( 'no_found_rows' => true, )); ?>

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

Попробуйте так:

<?php $foo_query = new WP_Query( array( 'post_type' => 'post', 'posts_per_page' => 30 + count( $posts_to_exclude ) ) ); if ( $foo_query->have_posts() ) : while ( $foo_query->have_posts() ) : $foo_query->the_post(); if ( in_array( get_the_ID(), $posts_to_exclude ) ) { continue; } the_title(); endwhile; endif; ?>

Вместо:

<?php $foo_query = new WP_Query( array( 'post_type' => 'post', 'posts_per_page' => 30, 'post__not_in' => $posts_to_exclude ) ); ?>

Подробнее можно прочитать на WordPress VIP

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

Использование аргумента cache_results => false в WP_Query почти всегда является плохой идеей. Если cache_results => true (а он по умолчанию true если у вас разрешено кэширование и настроен объектный кэш), WP_Query будет кэшировать найденные записи вместе с остальными данными. В очень редких случаях использование cache_results => false оправдано (например, при разработке собственных функций для WP-CLI).

Многоуровневые запросы стоит избегать. Примеры таких запросов:

  • Выборка записей из термов разных таксономий
  • Выборка записей значениям разных мета-полей

Пример 2-х уровневого запроса:

<?php 

// Запрашиваем записи из категории cat-slug с тегом tag-slug. 
new WP_Query( array( 'category_name' => 'cat-slug', 'tag' => 'tag-slug', )); 

?>

Каждый новый «слой» в запросе создает JOIN к дополнительной таблицы в базе данных. Старайтесь запрашивать записи с минимальным количеством параметров, а для сложных выборок и фильтраций используйте PHP

WP_Query vs. get_posts() vs. query_posts()

Как мы уже писали выше, get_posts() и WP_Query очень похожи, за исключением некоторых нюансов. Оба метода примерно одинаково «стоят» вам в плане производительности.

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

  • создает новый экземпляр объекта WP_Query с указанными вами параметрами.
  • заменяет собой существующий главный запрос (цикл)

Как отмечалось в Кодексеquery_posts() никогда не предназначался для использования в темах и плагинах, в первую очередь из-за способности к подмене и потенциальному дублированию главного запроса, что всегда негативно сказывается на производительности.

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

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

Лучший способ определить, есть ли нужное значение в массиве, или нет — это создавать правильные массивы. Такие, в которых можно быстро находить нужные ключи с помощью isset(). isset() имеет постоянную скорость поиска и поэтому хорошо масштабируется.

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

<?php
$array = array(
 'foo' => true,
 'bar' => true,
);
if ( isset( $array['bar'] ) ) {
  // value is present in the array
};

В случае, если вы не контролируете процесс создания массивов и вынуждены использовать in_array(), постарайтесь немного улучшить его производительность. Это можно сделать, указав 3 параметр strict : true, что заставит функцию использовать только строгое сравнение.

Кэширование

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

Объектное кэширование

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

Для работы с объектным кэшем, WordPress использует методы класса WP_Object_Cache. В дополнение к нему, Transients API (транзиты, транзитный кэш) отлично работает при кэшировании долгих запросов к базе данных, результатов работы сложных функций и тому подобных вещей.

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

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

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

В высоко-нагруженные проектах без постоянного кэширования следует избегать использования транзитов из-за риска заполнения таблиц wp_options большим количеством данных. См. секцию “Разумное хранение данных”.

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

Проще говоря, всегда проверяйте на наличие объекта в кэше и если его там нет — создавайте его на лету. Например:

<?php
/**
 * Выводим топ-10 самых комментируемых постов и кэшируем результат.
 *
 * @return array|WP_Error Array of WP_Post objects with the highest comment counts,
 *                        WP_Error object otherwise.
 */
function prefix_get_top_commented_posts() {
    // Check for the top_commented_posts key in the 'top_posts' group.
    $top_commented_posts = wp_cache_get( 'prefix_top_commented_posts', 'top_posts' );

    // If nothing is found, build the object.
    if ( false === $top_commented_posts ) {
        // Grab the top 10 most commented posts.
        $top_commented_posts = new WP_Query( 'orderby=comment_count&posts_per_page=10' );

        if ( ! is_wp_error( $top_commented_posts ) && $top_commented_posts->have_posts() ) {
            // Cache the whole WP_Query object in the cache and store it for 5 minutes (300 secs).
            wp_cache_set( 'prefix_top_commented_posts', $top_commented_posts->posts, 'top_posts', 5 * MINUTE_IN_SECONDS );
        }
    }
    return $top_commented_posts;
}
?>

В вышеприведенном примере есть проверка на наличие в кэше объекта с 10 самыми комментируемыми записями, которая запускает генерацию этого списка в случае, если в кэше ничего нет. Как правило, все запросы к WP_Query, кроме основного, следует кэшировать

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

Однако здесь есть нюанс. Перестройка кэша в примере выше всегда будет вызываться посетителем, попавшим на «просроченный» кэш. В этот момент для всех остальных пользователей эта страница будет грузиться дольше, причем задержка расти пропорционально количеству одновременных посетителей. Также, на высоконагруженных проектах есть риск получить «состояние гонки», когда несколько посетителей одновременно попадают на устаревший кэш и запускают его обновление. В худшем случае (большой трафик, неоптимизированные или сложные запросы к базе) «гонка» может привести к очередям на сервере базы данных, ошибкам записи и даже падению сайта из-за нехватки ресурсов, в первую очередь — оперативной памяти.

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

В WP есть хук, который срабатывает в нужном нам случае — wp_update_comment_count, который работает следующим образом: do_action( 'wp_update_comment_count', $post_id, $new, $old ).

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

Вот как это работает:

<?php
/**
 * "Разогрев" кэша 10 самых комментируемых записей.
 *
 * @param int $post_id Post ID.
 * @param int $new     The new comment count.
 * @param int $old     The old comment count.
 */
function prefix_refresh_top_commented_posts( $post_id, $new, $old ) {
    // Принудительное обновление кэша комментируемых записей
    prefix_get_top_commented_posts( $force_refresh = true );
}
add_action( 'wp_update_comment_count', 'prefix_refresh_top_commented_posts', 10, 3 );

/**
 * Получим 10 самых обсуждаемых записей и закэшируем результат.
 *
 * @param bool $force_refresh Опционально. Включает принудительное обновление кэша. По умолчанию выключен.
 * @return array|WP_Error Массив объектов WP_Post с самым большим количеством комментариев. Если не найдено, возвращает WP_Error.
 */
function prefix_get_top_commented_posts( $force_refresh = false ) {

    // Проверим на наличие ключа top_commented_posts в в кэше группе 'top_posts'
    $top_commented_posts = wp_cache_get( 'prefix_top_commented_posts', 'top_posts' );

    // Если ничего не найдено, создадим объект.
    if ( true === $force_refresh || false === $top_commented_posts ) {
        // Берем первые 10 самых комментируемых записей.
        $top_commented_posts = new WP_Query( 'orderby=comment_count&posts_per_page=10' );

        if ( ! is_wp_error( $top_commented_posts ) && $top_commented_posts->have_posts() ) {

            // Помещаем их в кэш, без срока экспирации.
            wp_cache_set( 'prefix_top_commented_posts', $top_commented_posts->posts, 'top_posts' );
        }
    }
    return $top_commented_posts;
}
?>

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

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

Полностраничное кэширование

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

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

Беткэш сделан для того, чтобы не дать потоку нового трафика положить ваш сайт. Он достигает этого тем, что отдает старые (по-умолчанию 5-минутной давности) страницы новым посетителям. Это уменьшает нагрузку на процессор и базу данных сервера. Но также это значит, что некоторые посетители вашего сайта будут получать не самые «свежие» страницы. Правда, это относится только к гостям сайта, которые никак не взаимодействуют с ним, а просто просматривают. Как только посетитель авторизуется или оставляет комментарий, он всегда будет получать свежие страницы.

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

  • Поскольку весь итоговый HTML кэшируется, вы не можете полагаться на серверную логику в отношении переменных $_SERVER$_COOKIE и других, которые уникальны для каждого отдельно взятого посетителя.
  • Вы можете, тем не менее, работать с cookie и другой пользовательской логикой на фронте, используя JavaScript

Batcache не отдает кэшированные страницы авторизованным пользователям (основываясь на нативных WordPress login cookies) — имейте это ввиду при разработке сайтов с высокой степенью интерактивности, таких как BuddyPress. Плагин также считает query в URL как часть адреса, т.е. страницы /your-page/ и /your-page?tracking=on будут фактически разными страницами. Это может значительно снизить эффективность кэширования на сайтах с отслеживанием UTM-меток (обычно используется в связке с Google Analytics). И не забывайте о том, что несмотря на использование плагина BatCache командой WordPress VIP, существуют определенные правила и условия его работы, которые не распространяются на публичную версию плагина.

Есть много популярных плагинов полностраничного кэширования, таких как W3 Total Cache и WP Super Cache, но мы не используем их по разным причинам. Тем не менее, для вашей конкретной ситуации они вполне могут подойти.

AJAX ендпоинты

Аббревиатура AJAX означает Асинхронный Яваскрипт и XML. Мы часто используем javascript в браузере для отправки запросов на эндпоинты, например для получение списка постов «бесконечного» скролла.

В WordPress встроено API для регистрации собственных AJAX-ендпоинтов на базе wp-admin/admin-ajax.php. По понятным причинам, WordPress не кэширует запросы внутри панели администрирования. Это значит, что отправляя любой запрос на admin-ajax.php, вы каждый раз загружаете ядро WordPress и минуете кэширование. Если это делать правильно, все будет хорошо. Но можно и «положить» сайт, просто отправляя запросы на admin-ajax.php с фронтенда.

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

Вот просто пример того, как можно спроектировать ендпоинт таким образом:

<?php
/**
 * Register a rewrite endpoint for the API.
 */
function prefix_add_api_endpoints() {
	add_rewrite_tag( '%api_item_id%', '([0-9]+)' );
	add_rewrite_rule( 'api/items/([0-9]+)/?', 'index.php?api_item_id=$matches[1]', 'top' );
}
add_action( 'init', 'prefix_add_api_endpoints' );

/**
 * Handle data (maybe) passed to the API endpoint.
 */
function prefix_do_api() {
	global $wp_query;

	$item_id = $wp_query->get( 'api_item_id' );

	if ( ! empty( $item_id ) ) {
		$response = array();

		// Do stuff with $item_id

		wp_send_json( $response );
	}
}
add_action( 'template_redirect', 'prefix_do_api' );
?>

Кэширование запросов к сторонним источникам

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

Вот небольшой пример того, как можно кэшировать подобные запросы:

<?php
/**
 * Получаем список записей другого блока и кэшируем тело ответа.
 *
 * @return string Тело ответа (body). Пустая строка в случае если ответа нет иди передан некорректный параметр.
 */
function prefix_get_posts_from_other_blog() {
    if ( false === ( $posts = wp_cache_get( 'prefix_other_blog_posts' ) ) {

        $request = wp_remote_get( ... );
        $posts = wp_remote_retrieve_body( $request );

        wp_cache_set( 'prefix_other_blog_posts', $posts, '', HOUR_IN_SECONDS );
    }
    return $posts;
}
?>

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

Разумное хранение данных

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

Каждый из перечисленных методов имеет свои особенности, влияющие на производительность:

  • Опции — API, работающее с опциями — это простая система хранения данных типа ключ-значение, использующая MySQL таблицу. Это API предназначалось для хранения небольших данных, таких как настройки цвета шапки или адреса сайта. Опции не созданы для хранения больших объемов произвольных данных. Производительность всего сайта может сильно пострадать от большого размера таблицы опций. Рекомендуется регулярно проверять таблицу wp_options и следить за тем, чтобы ее размер не превышал 500 строк. Поле “autoload” следует держать включенным (со значением ‘yes’) только для тех опций, которые действительно нужны вам в памяти для каждой страницы сайта. Кэширующие плагины также могут работать хуже с большими объемами опций в базе. Например, популярный плагин Memcached имеет ограничение в 1MB на размер отдельных элементов, хранящихся в кэше. Большая таблица опций может легко превысить этот лимит и значительно замедлить скорость загрузки страниц.
  • Post Meta (мета-поля) или Custom Fields (произвольные поля) — мета-поля по своей сути предназначены для хранения информации о конкретном посте. Например, если у нас есть тип записи «Продукт», то информация о серийном номере отлично подходит на то, чтобы записать ее в мета-поле. По этой причине обычно не имеет смысла делать выборки постов на основе метаполей. Это всегда будет медленнее, чем другие способы, потому что значения ключей в таблице post_meta не индексируются и для поиска по ним используется обычный полнотекстовый перебор строк в MySQL.
  • Таксономии и Термы. Таксономии — это средство группировки записей. Если у нас есть несколько записей, которые можно объединить в группу по какому-либо критерию, этот критерий является отличным кандидатом на термин таксономии. К примеру, для типа записей «Машина» можно создать таксономию «Производители», с термами «Nissan», «Ford», «Toyota» и т.д. Термы таксономий также хорошо подходят для поиска и сортировок на их основе, чего не скажешь о мета-полях.
  • Произвольные типы записей (Custom Post Types) — Архитектура WordPress основывается на сущности под названием «типы записей». “Запись” — это тоже тип записи, и поначалу это может сбивать с толку. Мы можем создавать свои собственные типы записей для хранения разных видов данных. Если нужно хранить произвольное количество объектов, таких как товары или футбольные матчи, произвольные записи будут хорошим вариантом.
  • Объектное кэширование (Object Cache) — см раздел вышеSee the “Caching” section.

И хотя теоретически возможно использовать нативное WordPress Filesystem API для записи/получения данных в файлы, делать это не рекомендуется по причине конфликта такого подхода с большинством современных хостинг-провайдеров.

Запись в базу данных

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

  • Старайтесь избегать запуска запросов на запись в базу с фронтенда. Это может негативно повлиять на производительность и вызывать условия «гонки» (одновременной отправки большого числа записей в базу, приводящее к потере или искажению данных)
  • Храните информацию в подходящих для этого местах. См. секцию Разумное хранение данных.
  • Опции сайта могут быть отмечены для «автозагрузки», т.е. загружаться в объектный кэш на каждой страницы. Создавая или изменяя опции, не забывайте об аргументе $autoload, который вы можете передать в функцию add_option(). Если ваша опция не используется часто, не стоит загружать ее на всем сайте с помощью автозагрузки. Начиная с WordPress 4.2, вы можете легко изменять порядок загрузки уже существующих опций с помощью 3 необязательного аргумента $autoload функции update_option(). Это проще и удобнее, чем удалять-добавлять опции лишь для того, чтобы поменять в них значение autoload.

Паттерны проектирования

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

Неймспейсы

Мы используем неймспейсы во всем PHP коде, кроме шаблонов темы. Это означает что каждый PHP файл, который не является частью Иерархии Шаблонов WordPress, должен быть часть неймспейса (или псевдо-неймспейса) так, чтобы его содержимое не конфликтовало с другими классами или функциями, имеющими одинаковые названия.

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

<?php
namespace TenUp\Buy_N_Large\Wall_E;

function do_something() {
  // ...
}

Идентификатор неймспейса состоит из неймспейса верхнего уровня (обычно это название компании, “Vendor Name”). Затем обычно идет название проекта или продукта, в примере выше это Buy_N_Large. Дополнительные уровни используются по желанию разработчиков проекта.

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

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

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

Объявления (declarations) use следует использовать для подключения классов, лежащих вне неймспейса выбранного файла. Лучше единожды указывать полные неймспейсы сторонних классов, чтобы потом обращаться к нему сразу по названию класса. Так значительно повышается читаемость кода и становятся наглядно видны зависимости. Это облегчит жизнь будущим разработчикам, которым придется разбираться в вашем коде.

<?php
/**
 * Пример использования декларации 'use'
 */
namespace TenUp\Buy_N_Large\Wall_E;
use TenUp\Buy_N_Large\Common\TwitterAPI;

function do_something() {
  // Плохо читаемо
  $twitter_api = new TenUp\Buy_N_Large\Common\TwitterAPI();
  // Уже лучше
  $twitter_api = new TwitterAPI();
}

Все, что объявляется в глобальном неймспейсе, включая сам неймспейс, должно быть уникальным. Неймспейс WordPressify — скорее всего, уникален. Theme — наверняка нет. Для дополнительной уникальности можно добавлять собственные префиксы к неймспейсам.

Проектирование объектов

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

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

<?php
/**
 * Video.
 *
 * Это объект "видео", включающий в себя обычные записи WordPress, а также     
 * различные мета-данные из YouTube API.
 *
 * @package    ClientTheme
 * @subpackage Content
 */
class Prefix_Video {

	/**
	 * Используем объект WordPress post для хранения данных.
	 *
	 * @access protected
	 * @var WP_Post
	 */
	protected $_post;

	/**
	 * Default video constructor.
	 *
	 * @access public
	 *
	 * @see get_post()
	 * @throws Возвращает ошибку если переданные данные не являются объектом (запись) или post ID.
	 *
	 * @param int|WP_Post $post Post ID or WP_Post object.
	 */
	public function __construct( $post = null ) {
		if ( null === $post ) {
			throw new Exception( 'Invalid post supplied' );
		}

		$this->_post = get_post( $post );
	}
}

Видимость

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

Архитектура и паттерны

  • Не рекомендуется использовать синглтоны. Существует немного причин, оправдывающих их применение, и на практике они создают больше проблем, чем пользы. Особенно при последующей поддержке вашего кода.
  • Зависимости классов следует использовать как можно чаще (по возможности). Таким образом достигается принцип DRY, когда ранее написанные компоненты используются многократно в разных местах вашего приложения
  • Глобальных переменных стоит избегать. Если объекты нужно передавать по всей теме или плагину, объявляйте их как параметры или ссылайтесь на них через фабрику объектов
  • Скрытые зависимости (функции API, супер-глобальные переменные и т.д.) должны также быть задокументированы в заголовке docblock у каждой функции/метода или свойства.
  • Избегайте регистрации хуков в методе __construct. Эта практика жестко связывает кухи и экземпляр класса и является менее гибкой, чем регистрация хука отдельным методом. Также, это значительно затрудняет юнит-тестирование.

Разделяйте плагины и темы с помощью add_theme_support

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

Лучший способ добиться этого — это использовать функции add_theme_support и current_theme_supports.

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

<?php
add_theme_support( 'custom-js-feature' );

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

<?php
if ( current_theme_supports( 'custom-js-feature' ) ) {
	// тут можно добавлять свой JS
}

Версионирование файлов

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

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

<?php
define( 'THEME_VERSION', '0.1.0' );

wp_register_script( 'custom-script', get_template_directory_uri() . '/js/asset.js', array(), THEME_VERSION );

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

Безопасность

Безопасность в контексте веб-разработки — очень обширная тема. Этот раздел обращает внимание только на те вещи, которые можно сделать на стороне сервера.

Валидация (validation) и санация (sanitization) вводимых данных

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

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

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

Вот пример валидации чисел (id), перед тем, как записать что-либо в мета-поле записи:

<?php
if ( ! empty( $_POST['user_id'] ) ) {
    if ( absint( $_POST['user_id'] ) === $_POST['user_id'] ) {
        update_post_meta( $post_id, 'key', absint( $_POST['user_id'] ) );
    }
}
?>

$_POST['user_id'] валидируется с помощью using absint(), которая проверяет что число >= 0. Без этого, $_POST['user_id'] может быть использован для внедрения вредоносного кода или данных в базу.

А вот пример санации (очистки) текстового поля перед сохранением его в базу данных:

<?php
if ( ! empty( $_POST['special_heading'] ) ) {
    update_option( 'option_key', sanitize_text_field( $_POST['special_heading'] ) );
}
?>

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

Подготовка и очистка прямых SQL запросов

Иногда бывают ситуации, когда вам придется писать прямые SQL запросы (хотя по-возможности этого лучше избегать). Для таких случаев, WordPress имеет класс $wpdb.

Прямые запросы в базу стоит проверять с особым вниманием. Они должны быть правильно подготовлены и очищены:

<?php
global $wpdb;

$wpdb->get_results( $wpdb->prepare( "SELECT id, name FROM $wpdb->posts WHERE ID='%d'", absint( $post_id ) ) );
?>

$wpdb->prepare() работает как sprintf() и по сути просто выполняет mysqli_real_escape_string() для каждого аргумента.

Функция mysqli_real_escape_string() оборачивает символы типа ' и ", предотвращая таким образом разные типы SQL атак.

Используя % в sprintf(), мы можем быть уверены что переданный аргумент является числом. Здесь вы можете спросить, зачем мы использовали еще и absint(), раз все уже очищено?. Да, но лучше иметь дополнительные слои очистки, чем пропустить что-то лишнее в базу по неосторожности.

А вот еще пример:

<?php
global $wpdb;

$wpdb->insert( $wpdb->posts, array( 'post_content' => wp_kses_post( $post_content ), array( '%s' ) ) );
?>

$wpdb->insert()создает новую строку в базе данных. $post_content передается в колонку post_content. Третий аргумент позволяет нам указывать формат, в стиле sprintf(). Можно принудительно указать, что передаваемое значение является строкой и предотвратить таким образом немало атак, связанных с SQL инъекциями. При этом, все равно стоит использовать wp_kses_post() для проверки $post_content на тот случай, если кто-нибудь решит передать вредоносный JavaScript код.

Экранирование (escape) или валидация (validate) вывода

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

WordPress имеет несколько встроенных в ядро функций, которые можно использовать для экранирования. Хорошей практикой является так называемое «позднее экранирование». Это означает, что мы экранируем (проверяем на корректность) данные прямо перед самым выводом на экран. Это улучшает читаемость кода и даст вам уверенность в том, что вы точно не пропустили ничего лишнего.

Простой пример позднего экранирования вывода:

<div>
   <?php echo esc_html( get_post_meta( $post_id, 'key', true ) ); ?>
</div>

esc_html() очищает вывод от любых HTML тегов, таким образом предотвращая JavaScript-инъекции или случайно «поехавшую» верстку.

Или вот еще пример, на этот раз валидации вывода:

<a href="mailto:<?php echo sanitize_email( get_post_meta( $post_id, 'key', true ) ); ?>">Email me</a>

sanitize_email() проверяет, является ли итоговая строка валидным email-адресом. Таким образом мы проводим валидацию. В данном примере можно было бы использовать более общую функцию, типа esc_attr(), но sanitize_email() подходит лучше.

Другой пример:

<input type="text" onfocus="if( this.value == '<?php echo esc_js( $fields['input_text'] ); ?>' ) { this.value = ''; }" name="name">

esc_js() позволяет убедиться, что возвращаемая строка безопасная для вывода внутри скрипта JavaScript. Эта функция изначально предназначалась для использования внутри тегов-аттрибутов, таких как onfocus="...".

Поскольку использование JavaScript внутри тегов больше не является рекомендуемой практикой, esc_js() также лучше не использовать. Существуют другие, более современные функции для экранирования JS.

Например:

<script>
if ( document.cookie.indexOf( 'cookie_key' ) >= 0 ) {
document.getElementById( 'test' ).getAttribute( 'href' ) = <?php echo wp_json_encode( get_post_meta( $post_id, 'key', true ) ); ?>;
}
</script>

wp_json_encode() служит для проверки строк перед выводом в JavaScript коде. Она проверяет, является ли возвращаемая строка безопасной для вставки в ваш скрипт. Функция возвращает строку в формате JSON.

Также,  wp_json_encode() добавляет оборачивающие кавычки к строке.

Иногда вам нужно экранировать данные, которые предполагается использовать как атрибут. Для этого есть специальная функция esc_attr(). Она проверяет, чтобы выводимая строка содержала только допустимые для использования внутри атрибутов символы.

<div class="<?php echo esc_attr( get_post_meta( $post_id, 'key', true ) ); ?>"></div>

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

<div>
 <?php echo wp_kses_post( get_post_meta( $post_id, 'meta_key', true ) ); ?>
</div>

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

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

А если вы все-таки используете wp_kses_* на фронтенде, итоговый результат следует кэшировать на максимально возможный срок.

Переведенные строки часто также нуждаются в экранировании вывода. Например так:

<div>
    <?php esc_html_e( 'An example localized string.', 'my-domain' ) ?>
</div>

Вместо встроенной в PHP функции __(), что-то вроде esc_html__() может быть более применимо к вашей ситуации.  Общие функции_e()также лучше заменять на esc_html_e().

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

Нонсы (Nonces)

В программировании есть концепция нонсов. Nonce — a number used only once, в переводе означает число, используемое только раз. Нонсы являются инструментов для борьбы с  CSRF — межсайтосов подделкой запросов.

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

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

Давайте представим, что вы хотите отправить в корзину запись с ID 1. Для этого вам потребуется перейти на страницу, типа https://example.com/wp-admin/post.php?post=1&action=trash

Поскольку вы уже зарегистрированы и авторизованы, злоумышленник может попытаться подменить ваш запрос на: https://example.com/wp-admin/post.php?post=2&action=trash

По этой причине действие удаления записи в WordPress требует наличие валидного нонса (токена).

После посещения https://example.com/wp-admin/post.php?post=1&action=trash&_wpnonce=b192fc4204, этот же нонс не будет работать для https://example.com/wp-admin/post.php?post=2&action=trash&_wpnonce=b192fc4204.

Обновления и удаления записей должны всегда требовать рабочий нонс.

Приведем пример кода для создания нонса:

<form method="post" action="">
    <?php wp_nonce_field( 'my_action_name' ); ?>
    ...
</form>

При обработке формы нонс должен быть проверен:

<?php
// Verify the nonce to continue.
if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'my_action_name' ) ) {
    // Nonce is valid!
}
?>

Поддержка переводов

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

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

Самуэль Вуд (Отто) собрал воедино все лучшие практики по переводам в WordPress и вам стоит уделить время и ознакомиться с этим гайдом: Internationalization: You’re probably doing it wrong

Здесь важно отметить, что данные, которые передаются в функции перевода, всегда должны быть именно строками. Не переменными или константами, а строками. Также не стоит использовать интерполяцию строк для вставки значений переменных в строки. Большинство инструментов для создания переводов используют библиотеку GNU gettext. При этом PHP код не запускается, а лишь сканируется и проверяется на возможность перевода, точно также как обычный текст. Если API переводов WordPress не могут точно сопоставить строку с шаблонами для переводов, перевести строку не получится. Чтобы избежать этого, используете printf formatting codes внутри строк, предназначенных для перевода, и передавайте переведенную версию строки в sprintf() для вывода значений переменных.

Вот как это работает:

<?php

// Это запутает программы перевода
$string = __( "$number minutes left", 'plugin-domain' );

// И это тоже
define( 'MINUTES_LEFT', '%d minutes left' );
$string = __( MINUTES_LEFT, 'plugin-domain' );
// Правильный способ перевести простую строку

$string = sprintf( __( '%d minutes left', 'plugin-domain' ), $number );

// Более сложный подход, позволяющий переводить множественные значения с помощью переменной _n()
$string = sprintf( _n( '%d minute left', '%d minutes left',  $number, 'plugin-domain' ), $number );
?>

При локализации проектов также важно помнить следующие вещи:

  • Использовать уникальный text domain во всех функциях перевода
  • Переведенные участки обязательно нужно экранировать при выводе

Текстовые домены

Каждый проект должен иметь собственные уникальные текстовые домены для строк. Текстовые домены (text domains) следует писать на латинице, используя буквы, цифры и тире для разделения нескольких слов. Например: tenup-project-name.

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

<?php

// Только так
$string = __( 'Hello World', 'plugin-domain' );

// А не так
$string = __( 'Hello World', $plugin_domain );

// Или так
define( 'PLUGIN_DOMAIN', 'plugin-domain' );
$string = __( 'Hello World', PLUGIN_DOMAIN );
?>

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

Текстовый домен также нужно указывать в заголовке “Text Domain” главного файла плагина или файла стилей темы, чтобы обширное сообщество WordPress могло начать добавлять собственные версии переводов с помощью GlotPress.

Экранирование строк

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

Для облегчения этой задачи, WordPress API имеет функции, способные одновременно переводить и экранировать строки. Мы рекомендуем разработчикам использовать именно эти функции:

Для переводов в HTML

  1. esc_html__: возвращает переведенную и экранированную строку
  2. esc_html_e: выводит переведенную и экранированную строку
  3. esc_html_x: возвращает переведенную и экранированную строку, передает контекст в функцию переводов gettext

Для переводов атрибутов

  1. esc_attr__: возвращает переведенную и экранированную строку
  2. esc_attr_e: выводит переведенную и экранированную строку
  3. esc_attr_x: возвращает переведенную и экранированную строку, передает контекст в функцию переводов gettext

Оформление кода и документация

Мы следуем официальным стандартам оформления кода и документации, принятым в мире WordPress. Расширение WordPress Coding Standards for PHP_CodeSniffer позволяет находить большинство распространенных отклонений от стандарта и помечать некорректный код для ручной оценки.

Также, мы крайне рекомендуем подробно комментировать и документировать весь код, с акцентом на использование длинных описаний в docblock секциях. Эти описания должны отвечать на 2 главных вопроса: «Почему этот код здесь?» и «Что именно делает этот код», причем делать это понятным обычному человеку языком. Хорошим считается такое описание, которое может понять ваш менеджер (не программист), просто прочитав docblock и сопутствующие инлайновые комментарии.

Например, такое:

<?php
/**
 * Hook into WordPress to mark specific post meta keys as protected
 *
 * Post meta can be either public or protected. Any post meta which holds
 * **internal or read only** data should be protected via a prefixed underscore on
 * the meta key (e.g. _my_post_meta) or by indicating it's protected via the
 * is_protected_meta filter.
 *
 * Note, a meta field that is intended to be a viewable component of the post
 * (Examples: event date, or employee title) should **not** be protected.
 */
add_filter( 'is_protected_meta', 'protect_post_meta', 10, 2 );

/**
 * Protect non-public meta keys
 *
 * Flag some post meta keys as private so they're not exposed to the public
 * via the Custom Fields meta box or the JSON REST API.
 *
 * @internal                          Called via is_protected_meta filter.
 * @param    bool   $protected        Whether the key is protected. Default is false.
 * @param    string $current_meta_key The meta key being referenced.
 * @return   bool   $protected        The (possibly) modified $protected variable
 */
function protect_post_meta( $protected, $current_meta_key ) {

    // Assemble an array of post meta keys to be protected
    $meta_keys_to_be_protected = array(
        'my_meta_key',
        'my_other_meta_key',
        'and_another_meta_key',
    );

    // Set the protected var to true when the current meta key matches
    // one of the meta keys in our array of keys to be protected
    if ( in_array( $current_meta_key, $meta_keys_to_be_protected ) ) {
        $protected = true;
    }

    // Return the (possibly modified) $protected variable.
    return $protected;
}
?>

Юнит-тестирование и интеграционное тестирование

Юнит-тестирование заключается в автоматическом тестировании юнитов (элементов) вашего исходного кода на основании заранее написанных сценариев. Цель юнит-тестирования состоит в создании таких сценариев, которые смогут ответить на вопрос: «действительно ли тестируемый участок кода работает так, как задумывалось?». Если тест не пройден, обнаруживается потенциальная проблема и код отправляется на доработку.

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

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

Подробнее про тестирование можно прочитать на странице PHPUnit и в разделе автоматизированное тестирование в WordPress.

Библиотеки и фреймворки

Как правило, для разработки под WordPress лучше не использовать PHP фреймворки или библиотеки, которые не включены в ядро. API WordPress предоставляют 99% всех функций, необходимых в ежедневной работе: от работы с базой данных до отправки емейлов. Также в него входят другие открытые библиотеки и фреймворки (например, PHPUnit), которые можно использовать при создании тем и плагинов.

Избегайте Heredoc и Nowdoc

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

$y = <<<JOKE
I told my doctor
"it hurts when I move my arm like this".
He said, "<em>then stop moving it like that!</em>"
JOKE;

Однако, синтаксис heredoc/nowdoc делает невозможным применение практики позднего экранирования (late escaping):

// Раннее экранирование
$a = esc_attr( $my_class_name );

// Что-то некрасивое может произойти здесь, уже после экранирования
$a .= 'something naughty';

// Поэтому хорошо бы попробовать экранировать сразу в месте вывода, вот здесь
echo <<<HTML
<div class="test {$a}">test</div>
HTML;

Какими бы удобными они вам не казались, следует избегать использования синтаксисов heredoc/nowdoc в реальных проектах, заменяя их стандартными методами конкатенации (объединения) и вывода строк. Читать HTML в таком случае несколько сложнее. Но зато мы можем быть уверенными в том, что экранирование сработает как надо в месте вывода, независимо от состояния переменных до этого.

// Здесь могли испортить строку...
$my_class_name .= 'something naughty';

// Но это неважно, потому что мы практикуем позднее экранирование
echo '<div class="test ' . esc_attr( $my_class_name ) . '">test</div>';

А еще лучше будет использовать встроенную в WordPress функцию get_template_part() в качестве базового шаблонизатора. Создавайте шаблоны как можно более чистыми. Пусть в них был только HTML код и немного <?php ?> вставок там, где вам нужно выводить и экранировать данные. Итоговый файл будет иметь такую же читаемость, как блоки heredoc/nowdoc, но вы сможете практиковать позднее экранирование в самом шаблоне.

Избегайте сессий

PHP-сессии добавляют сложности веб-приложениям и создают дополнительную нагрузку на сервера. Хранение уникальных пользовательских данных (да и вообще любых данных) в них считается плохой практикой. Команда WordPress VIP вообще запрещает использование сессий и отдельно проверяет это во время код-ревью своих проектов.

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

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

Сессии никогда не должны храниться в базе данных. Это очень плохо сказывается на производительности, по той простой причине что MySql никогда не предназначался для хранения сессий. Также, PHP-библиотеки, созданные для хранения сессий в базе, никогда не будут такими же быстрыми, как нативные функции PHP для работы с сессиями. Расширения PHP для Memcache и Redis позволяют хранить сессии в памяти, что само по себе гораздо быстрее хранения в базе данных и видится хорошим решением.

Павел Федоров

Создатель этого сайта и многих других (на WordPress, конечно же). Любит WordPress и делает на нем всякие сумасшедшие сложные штуки, которые никто в здравом уме делать не станет. Умеет работать на фрилансе, в офисе, без офиса, без оглядки и без сна. Один из немногих участников программы FSA/FLEX, кого выдворили из Америки за плохое поведение. С тех пор умеет слушать. Обожает Star Wars, Ведьмака, горные лыжи, байдарку, пешие прогулки, спонтанные путешествия и хорошую компанию. Больше не боится vim.

1 комментарий к “Лучшие практики PHP-разработки на WordPress”

Добавить комментарий

%d такие блоггеры, как: