WordPress как на ладони
rgbcode is looking for WordPress developers. Очень Удобный и Быстрый Хостинг для сайтов на WordPress. Пользуюсь сам и вам рекомендую!

HTTP API WordPress

В PHP есть несколько способов отправить HTTP запрос, в WordPress только один. Но этот один способ включает в себя все варианты поддерживаемые PHP - это и есть API, это стандарт и это удобно!

Знакомство с HTTP API

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

  1. Привет сервер, можешь мне показать файл: file.html?
  2. Привет! Могу, вот он...

Выглядит такой диалог вот так (только на месте клиента в данном случае выступает наш сервер, который делает запрос на другой сервер):

ch-negotiation

При создании HTTP запросов в PHP, как правило используются один из вариантов: библиотека cURL или встроенные в PHP потоки (streams). Чтобы упростить и стандартизировать разные способы отправки запросов, с версии 2.7. в WordPress появился класс WP_Http, который и лег в основу HTTP API.

Зачем нужен HTTP API

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

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

Еще один плюс HTTP API в том, что мы имеет единый стандарт указываемых данных, при работе с разными типами транспортировки запросов, т.е. мы всегда указываем одинаковые параметры и передаем их в функцию HTTP API, а класс уже выбирает подходящий тип транспорта, например cURL, изменяет наши параметры под понятные для текущего типа транспорта, и отправляет запрос.

С версии 2.7. HTTP API работал только с базовыми элементами запроса: header, body и response. С версии 2.8. появились:

  • сжатие (compression).
  • куки (cookies).
  • поддержка прокси (proxy).

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

Библиотека Requests

С версии WP 4.6 HTTP API работает на базе библиотеки Requests, которая разрабатывается и поддерживается WordPress.

Так, ядро WP_Http полностью было заменено на PHP библиотеку Requests и теперь все запросы выполняются через нее.

Таким образом, технически HTTP API WordPress кардинально изменился, но интерфейс остался прежнем: все работает как и работало.

С внедрением библиотеки Requests ожидаются новые возможности, некоторые из них уже появились (нечувствительные к регистру заголовки, поддержка международных доменов вроде böcean.ch). А другие (параллельные запросы) появятся в следующих релизах и пока возможны только с использованием библиотеки Requests напрямую.

С WP 4.6. Классы WP_Http_Curl и WP_Http_Streams уже не используются.

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

HTTP API WordPress сегодня

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

Почти все возможности транспорта, можно изменять через опции или фильтры. Например, через фильтр http_api_transports можно добавить еще один, свой класс транспорта. Или через установку констант в файл wp-config.php можно включить режим прокси:

define('WP_PROXY_HOST', '192.168.84.101');
define('WP_PROXY_PORT', '8080');
define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
define('WP_PROXY_USERNAME', 'login');
define('WP_PROXY_PASSWORD', 'pass');

Чтобы разобраться, как работает прокси смотрите класс WP_HTTP_Proxy{}

HTTP API можно расширить, для работы с Twitter API, Google Maps API и т.д.

Функции HTTP API и отправка запроса

Использовать HTTP API очень просто, для этого существуют специальные функции API:

Функции отправки запросов:

Все функции возвращают: массив или WP_Error.

  • Результат возвращается в виде массива и содержит все данные ответа: контент (тело) ответа
  • WP_Error возвращается в случае неудачного запроса. Если сервер вернул нестандартную ошибку: 500, 404 и т.д., то вы получите данные ответа, а не WP_Error..

Функции обработки ответа:

Параметры функций

$url(строка) (обязательный)
URL куда будет отправлен запрос (с которого будут получены данные).
$args(массив)
Параметры запроса. Список параметров смотрите в описании wp_remote_request()
По умолчанию: array() (предустановки)
$response(массив)
Результат запроса возвращаемый любой функцией запроса.

Базовый пример использования

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

$response = wp_remote_get('http://httpbin.org/get?a=b&c=d');

Тут, мы отправили запрос на URL: http://httpbin.org/get и добавили два параметра запроса ?a=b&c=d. В результате, $response будет содержать следующий массив:

Array (
	[headers] => Array (
			[server] => nginx
			[date] => Sun, 19 Jun 2016 08:18:20 GMT
			[content-type] => application/json
			[content-length] => 316
			[connection] => close
			[access-control-allow-origin] => *
			[access-control-allow-credentials] => true
		)

	[body] => {
		"args": {
			"a": "b",
			"c": "d"
		},
		"headers": {
			"Accept": "*/*",
			"Accept-Encoding": "deflate;q=1.0, compress;q=0.5, gzip;q=0.5",
			"Host": "httpbin.org",
			"User-Agent": "WordPress/4.5.2; http://wp-kama.ru"
		},
		"origin": "5.101.156.80",
		"url": "http://httpbin.org/get?a=b&c=d"
	}

	[response] => Array (
			[code] => 200
			[message] => OK
		)

	[cookies] => Array()

	[filename] =>

	[http_response] => WP_HTTP_Requests_Response Object (
		...
	)
)

Как видно, результат содержит ключи, каждый ключ - это часть ответа сервера, где:

  • headers - содержит заголовки HTTP ответа сервера.
  • body - тело ответа. Если мы запрашиваем html страницу, то тут будет HTML код. Если возвращаются JSON данные, то тут будет JSON строка. И т.д.
  • response - содержит статус код HTTP ответа. Например 200 - OK.
  • cookies - содержит куки, которые просит установить сервер в ответе на запрос.
  • filename - содержит расположение или путь файла который был отправлен на сервер и был там размещен. Файл можно отправить POST запросом.
  • http_response - с версии WP 4.6. Cодержит весь объект результата запроса библиотеки Requests.

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

$response = wp_remote_get('http://httpbin.org/get?a=b&c=d');
$headers = $response['headers'];

echo $headers['server']; //> nginx
echo $headers['content-type']; //> application/json
echo $headers['content-length']; //> 316

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

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

$response = wp_remote_get('http://httpbin.org/get?a=b&c=d');

echo wp_remote_retrieve_header($response, 'server'); //> nginx
echo wp_remote_retrieve_header($response, 'content-type'); //> application/json
echo wp_remote_retrieve_header($response, 'content-length'); //> 316

На этом теоретическую часть HTTP API можно закончить и перейти к примерам.

Примеры использования WP HTTP API

#1 Передача параметров запроса в GET и POST

Для GET запроса аргументы передаются в самом URL:

$url = 'http://httpbin.org/get';

// параметры запроса
$params = array(
	'foo' => 'значение 1',
	'bar' => 10
);

// чтобы кириллица правильно передалась
$params = urlencode_deep( $params );

// Добавим параметр в URL
$url = add_query_arg( $params, $url );

// в результате получим такой URL:
// http://httpbin.org/get?foo=%D0%B7%D0%B0%D0%BD%D1%87%D0%B5%D0%BD%D0%B8%D0%B5+1&bar=10

// отправляем запрос
$response = wp_remote_get( $url );

$body = wp_remote_retrieve_body( $response );

print_r( $body );

Для POST запроса, параметры запроса передаются в опции body:

$url = 'http://httpbin.org/post';

// параметры запроса
$args = [
	// параметры запроса
	'body' => [
		'foo' => 'значение 1',
		'bar' => 10,
	],
	'timeout' => '5',
];

$response = wp_remote_post( $url, $args );

#2 Изменение параметров запроса

Перед отправкой запроса, можно изменить дефолтные настройки запроса. Например, установить timeout (максимальное время) соединения или указать user agent.

Полный список параметров (настроек) смотрите в описании wp_remote_request().

А этот пример демонстрирует, как передавать такие настройки:

$url = 'http://httpbin.org/get';

$args = array(
	'timeout' => 5, // Время в секундах на получение данных.
	'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
);

$response = wp_remote_get( $url, $args );

#3 Получение тела ответа (наше любимое)

Этот пример показывает как передать в запрос дополнительный заголовок и получить тело (текст) ответа.

$url = 'http://httpbin.org/get?a=b&c=d';

// Передача заголовка в данном случае смысла не имеет - это просто демонстрация.
$args = array(
	'headers' => array( "Content-type" => "application/json" )
);

$response = wp_remote_get( $url, $args );

$body = wp_remote_retrieve_body( $response );

#4 Получение заголовков ответа

#1 Делаем полный запрос и получаем из него заголовки

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

$response = wp_remote_get('http://httpbin.org/get?a=b&c=d');

// получаем только заголовки ответа
$response_headers = wp_remote_retrieve_headers( $response ); //> массив со всеми заголовками

// получаем отдельный заголовок ответа
$content_type = wp_remote_retrieve_header( $response, 'content-type' ); //> application/json
#2 Запрашиваем только заголовки

В этом примере мы не будем создавать полный запрос, а запросим только заголовки. Для этого воспользоватся функцией запроса wp_remote_head():

$response = wp_remote_head('http://httpbin.org/get?a=b&c=d');

$response_headers = wp_remote_retrieve_headers( $response ); //> массив со всеми заголовками

Ответ на такой запрос мы получим быстрее, чем при «полном запросе». Поэтому, если вам не нужно тело ответа, то пользуйтесь таким запросом.

#3 Запрашиваем только заголовки (способ 2)

Используя функцию wp_get_http_headers(), мы также можем не создавать полный запрос, а запросить и сразу получить только заголовки:

$response_headers = wp_get_http_headers('http://wordpress.org');

print_r( $response_headers );

/*
Array
(
	[server] => nginx
	[date] => Wed, 22 Jun 2016 02:03:20 GMT
	[content-type] => application/json
	[content-length] => 316
	[connection] => close
	[access-control-allow-origin] => *
	[access-control-allow-credentials] => true
)
*/

2 и 3 пример, никогда не содержать тело ответа и их использование может пригодится для высоко-нагруженных API, которые ограничивают число запросов в период времени. Так, перед тем как получить тело запроса, нужно проверить: а были ли изменены данные, отвечает ли сервер на наш запрос и т.д. В таких случаях, рекомендуется сначала отправить HEAD запрос, обработать ответ, где проверить есть ли новые данные и только потом делать полный запрос с получением тела ответа.

#4 Дебаг: данные запроса

Variant 1:

Чтобы покопаться в WordPress HTTP API, я рекомендую следующий фрагмент с использованием события http_api_debug:

add_action( 'http_api_debug', function( $response, $type, $class, $args, $url ) {

	// You can change this from error_log() to var_dump() but it can break AJAX requests
	echo sprintf( "Request URL: %s\n", print_r( $url, 1 ) );
	echo sprintf( "Request Args: %s\n", print_r( $args, 1 ) );
	echo sprintf( "Request Response : %s\n", print_r( $response, 1 ) );

}, 10, 5 );
Variant 2:

Иногда нужно получить данные запроса, которые будут отправлены непосредственно перед запросом. Самый поздний хук который мне удалось найти, сразу после которого делается сам запрос на основе определившегося $transport это хук requests-(hook) - requests-requests.before_request:

add_action( "requests-requests.before_request", function( $parameters, $request, $url ){

	/* $parameters
		https://example.com/wp-json/wp/v1/post/456?
	*/

	/* $request
	Array
	(
		[Authorization] => Basic a2FtYTp1a2RDFTHWjQgYTRVRyBYVjJzIFU5STIgeW5oiu==
		[Cookie] => my_cookie=cookie_value
	)
	*/

	/* $url
		null
	*/

}, 10, 3 );

Далее после того как будет получен транспорт - Requests::get_transport() и сделан запрос, отработает хук requests-requests.before_parse, на котором можно получить данные ответа (впрочем данные ответа можно получить и стандартным образом):

add_action( "requests-requests.before_parse", function( $parameters, $request, $url ){

	// $parameters
	/*
		HTTP/1.1 200 OK
		Date: Mon, 23 May 2022 10:02:26 GMT
		Server: Apache/2.4.41 (Ubuntu)
		Upgrade: h2,h2c
		Connection: Upgrade, close
		X-Cache: miss
		X-Robots-Tag: noindex
		Link: ; rel="https://api.w.org/"
		X-Content-Type-Options: nosniff
		Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages, Link
		Access-Control-Allow-Headers: Authorization, X-WP-Nonce, Content-Disposition, Content-MD5, Content-Type
		Expires: Wed, 11 Jan 1984 05:00:00 GMT
		Cache-Control: no-cache, must-revalidate, max-age=0
		Allow: GET, POST
		Content-Length: 1630
		Content-Type: application/json; charset=UTF-8

			RESPONSE Content ...
	*/

	// $request
	/*
	 https://example.com/wp-json/wp/v1/post/456?
	*/

	// $url
	/*
	Array
	(
		[Authorization] => Basic a2FtYTp1a2RDFTHWjQgYTRVRyBYVjJzIFU5STIgeW5oiu==
		[Cookie] => my_cookie=cookie_value
	)
	*/

}, 10, 3 );

#6 Получение кода ответа

$response = wp_remote_get('http://httpbin.org/get?a=b&c=d');

echo wp_remote_retrieve_response_code( $response ); //> 200
echo wp_remote_retrieve_response_message( $response ); //> OK

#7 Произвольный тип запроса

Кроме GET и POST типов запросов, мы можем отправлять любой другой, например, PUT или DELETE.

Для этого используем функцию wp_remote_request(), которая лежит в основе всех функций запросов:

$url = 'http://httpbin.org';

$args = array(
	'method' => 'PUT',
	//'method' => 'DELETE'
);

$response = wp_remote_request( $url, $args );

#8 Получение данных о фильме с Кинопоиска

Этот пример показывает, как получить данные фильма с Кинопоиска, по указанному ID фильма.

/**
 * Получим информацию о фильме с сайта Kinopoisk
 * Работает на базе сервиса http://docs.kinopoiskapi.apiary.io/.
 *
 * @param int $id ID фильма на сайте
 * @return string|WP_Error Получает тело ответа, в случае успеха и объект WP_Error при неудаче
 */
function kinopoisk_get_movie( $id ) {

	// Параметры GET запроса
	$params = array(
		'filmID' => absint( $id ),
	);

	// Создадим URL с параметрами
	$url = 'http://api.kinopoisk.cf/getFilm';
	$url = add_query_arg( $params, esc_url_raw($url) );

	// запрос
	$response = wp_remote_get( $url );

	// Проверяем код ответа
	$response_code    = wp_remote_retrieve_response_code( $response );
	$response_message = wp_remote_retrieve_response_message( $response );
	$response_body    = json_decode(wp_remote_retrieve_body( $response ));

	if ( 200 != $response_code && ! empty( $response_message ) )
		return new WP_Error( $response_code, $response_message );

	elseif ( 200 != $response_code )
		return new WP_Error( $response_code, 'Неизвестная ошибка' );

	elseif( ! $response_body )
		return new WP_Error( 'nodata', 'Нет данных о фильме или такого фильма нет в базе' );

	else
		return $response_body;
}

// Пример использования функции ---

// Запрос
$res = kinopoisk_get_movie( 714888 );

// Выводим ошибку или информацию
if ( is_wp_error( $res ) )
	echo 'Ошибка при запросе к IMDB: '. wp_strip_all_tags( $res->get_error_message() );

else
	echo 'Фильм: "' . esc_html( $res->nameRU ) .'" ('. (int) $res->year .' год). Рейтинг: '. $res->ratingData->rating;

// в результате получим:
// Фильм: "Звёздные войны: Пробуждение силы" (2015 год). Рейтинг: 7.3

#9 Управление кэшированием

Для управления кэшированием нужно указать заголовок Cache-Control:

$readme_url = 'http://example.com/foo';
$http_params = [
	'headers' => [
		'Authorization' => "token $auth_token",
		'Cache-Control' => 'no-cache',
	],
];

$res = wp_remote_get( $readme_url, $http_params );

#10 Хук разрешаем внутренние запросы с htpasswd

Допустим на сервере установлен пароль через файл .htpasswd, см. заметку. В этом случае, крон задачи перестанут работать и возможно другие внутренние запросы. Чтобы решить эту проблему, можно установить дефолтные параметры авторизации для всех запросов WP HTTP API:

if( 'production' !== wp_get_environment_type() ){

	add_filter( 'http_request_args', function ( $parsed_args, $url ){

		if( str_contains( $url, parse_url( home_url(), PHP_URL_HOST ) ) ){

			$log_pass = 'login:password';

			$auth = & $parsed_args['headers']['Authorization'];

			if( ! $auth ){
				$auth = 'Basic ' . base64_encode( $log_pass );
			}

		}

		return $parsed_args;
	}, 10, 2 );

}

Этот код можно разместить, например, в must-use плагины.

#11 Пример использования прокси

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

$proxy = '180.168.232.64:8001';
$proxyauth = 'k7yQJa:R9Cdeu';
$url = 'https://example.com';

[ $host, $port ] = explode( ':', $proxy );
[ $log, $pass ] = explode( ':', $proxyauth );

define( 'WP_PROXY_HOST', $host );
define( 'WP_PROXY_PORT', $port );
define( 'WP_PROXY_USERNAME', $log );
define( 'WP_PROXY_PASSWORD', $pass );
//define( 'WP_PROXY_BYPASS_HOSTS', 'example.com, *.wordpress.org' );

$resp = wp_remote_get( $url );
$headers = wp_remote_retrieve_headers( $resp );
$code = wp_remote_retrieve_response_code( $resp );
$body = wp_remote_retrieve_body( $resp );

var_dump( $code );
print_r( $headers );
echo $body;

Чтобы работать по протоколам socks5, socks4 нужно чтобы они были включены (их поддерживал) CURL сервера, а также нужно будет хаком указать их форсированно. Пример как это сделать:

add_action( 'http_api_curl', 'wpkama_curl_before_send', 100, 3 );

function wpkama_curl_before_send( $handle, $request, $url ) {

	curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5 );
	// curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4 );
	// curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4A );
}

Если делать запрос стандартно через CURL, то он будет выглядеть так:

$proxy = '180.168.232.64:8001';
$proxyauth = 'k7yQJa:R9Cdeu';
$url = 'https://example.com';

$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_PROXY, $proxy );
curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxyauth );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_HEADER, 1 );
$curl_scraped_page = curl_exec( $ch );
curl_close( $ch );

echo $curl_scraped_page;

Недорогие прокси можно купить тут: proxy6.net. Введите партнерский код RgJ8zx8ycP и получите 5% скидку.

Сохранение результата запроса (кэширование)

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

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

Дело в том, что при использовании одной из функций HTTP API наш сервер отправляет запрос на другой сервер и ждет ответа. В период ожидания, никакие PHP операции на нашем сервере не происходят - он просто останавливает выполнение скрипта и ждет. Такое ожидание может быть довольно долгим. Как правило, такое ожидание длится от 0,5 до 5 секунд.

Картина выглядит примерно так: нам на сервер пришел HTTP запрос: http://example.com/page, и наш сервер должен показать страницу page. Для этого наш сервер начинает генерировать эту страницу, при этом до выполнения каких либо операций по генерации страницы, подключаются различные модули: nginx, apach, PHP, модули PHP и т.д. Затем начинается генерация страницы, и чем сложнее страница, тем дольше она будет генерироваться. Но при этом, сервер использует свои данные из оперативной памяти, с диска, с базы данных и т.д. все эти данные он получает за доли секунды. А теперь представим, что в какой-то момент сбора данных и генерации страницы, нужно получить данные с другого сервера. В этот момент собирается запрос и отправляется, и наш сервер ждет ответа: пока запрос дойдет до другого сервера, пока там соберутся все модули, пока страница (ответ) сгенерируется и пока он вернется к нам через все узлы интернет соединения. Все это по меркам сервера, очень долго - от 0,5 до 5 секунд. А что если таких запросов несколько, например для данных в какой-нибудь таблице?

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

Другая проблема: «пропускная способность»

Теоретически можно обойтись без кэширования, если у вас на странице всего один HTTP запрос и сервер обрабатывающий этот запрос позволяет отправлять ему неограниченное количество запросов. Но как правило, если вы используете какой-то API, на сервере где обрабатываются запросы, стоит ограничение на количество запросов в промежуток времени, так называемая «пропускная способность» API. И в какой-то момент, удаленный сервер просто не ответит на ваш запрос, а вы не получите нужных данных.

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

Кэширование во временные опции (transients)

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

Где:

$transient(строка) (обязательный)
Название временной опции. Максимальная длина 172 знака. Ожидаются данные с экранированными слэшами, очистка слэшей делается автоматически перед записью в БД. Функция автоматически защищает от SQL инъекций.
$value(смешанный) (обязательный)
Значение временной опции. Ожидаются данные с экранированными слэшами, очистка слэшей делается автоматически перед записью в БД. Функция защищает от SQL инъекций. Сериализация, если передается массив, также делается автоматически.
$expiration(строка/массив/число/объект/логический)

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

Если указать 0 - опция никогда не удалиться и не будет иметь срока давности. Получается это будет обычная опция, только с префиксом _transient_ в её названии - это будет означать, что она временная и при чистке таблицы wp_options её можно будет смело удалять, без риска, что-то испортить.
По умолчанию: 0

Важный момент про временные опции (transients). Они умеют работать с объектным кэшированием. Т.е. они записываются на время в базу данных wp_options. Но если на сайте установлен модуль (плагин) объектного кэширования, то они не записываются в БД, а сохраняются в файлы объектного кэша.

#1 Примеры кэширования HTTP запроса во временную опцию

Допустим, через HTTP запрос мы получаем курс рубля со стороннего сайта. Чтобы ускорить загрузку страницы, нам нужно кэшировать полученный результат на 12 часов.

$transient = 'one_usd_in_rub'; // название временной опции

$usd_in_rub = get_transient( $transient ); // пробудем получить сохраненные данные

// если данных нет, или они просрочены, получим и сохраним их
if( ! $usd_in_rub ){
	$url = 'http://www.cbr.ru/scripts/XML_daily.asp';
	$expiration = DAY_IN_SECONDS / 2; // время жизни кэша - пол дня

	$resp = wp_remote_get( $url ); // получим данные

	// если статус ответа 200 - ОК
	if( wp_remote_retrieve_response_code($resp) === 200 ){
		$body = wp_remote_retrieve_body( $resp );

		$xml = simplexml_load_string($body); // парсим XML данные
		$data = json_decode(json_encode($xml)); // превратим в обычный объект

		//print_r( $data );

		$USD = wp_list_filter( $data->Valute, array('CharCode'=>'USD') ); // получим только данные USD
		$USD = array_shift($USD);

		/*
		stdClass Object (
			[NumCode] => 840
			[CharCode] => USD
			[Nominal] => 1
			[Name] => Доллар США
			[Value] => 64,0165
		)
		*/

		// echo "$USD->Nominal $USD->Name равен $USD->Value руб."; //> 1 Доллар США равен 64,0165 руб.

		$usd_in_rub = $USD->Value;
	}
	// статус ответа не 200 - ОК
	else
		$usd_in_rub = 'нет данных';

	// сохраним курс доллара во временную опцию
	set_transient( $transient, $usd_in_rub, $expiration );
}

echo 'Курс доллара к рублю на сегодня: $1 = '. $usd_in_rub .' руб.';
//> Курс доллара к рублю на сегодня: $1 = 64,0165 руб.

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

Удаление временной опции

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

delete_transient('название_временной_опции');

Кэширование в метаполя

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

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

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

#1 Примеры кэширования HTTP запроса в метаполя

Для примера, возьмем аналогию, где записи это фильмы и для получения данных фильма с Кинопоиска используется функция kinopoisk_get_movie( $id ) из примера выше. Также, предположим, что для каждой записи у нас уже есть ID фильма на Кинопоиске и находится он в метаполе kinopoisk_id.

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

function get_film_rating( $post_id = 0 ){
	if( ! $post_id ) $post_id = $GLOBALS['post']->ID;

	$rating  = get_post_meta( $post_id, 'kinopoisk_rating', 1 );
	$up_time = get_post_meta( $post_id, 'kinopoisk_rating_uptime', 1 );

	// проверим нужно ли обновить данные
	if( time() > $up_time + WEEK_IN_SECONDS ){
		// время просрочено или нет данных в метаполях

		$film_id = get_post_meta( $post_id, 'kinopoisk_id', 1 );

		// пробуем получить данные с Кинопоиска
		$res = kinopoisk_get_movie( $film_id );

		// удалось получить данные!
		if( ! is_wp_error($res) && $res->ratingData->rating )
			$rating = $res->ratingData->rating;
		// ошибка при получении данных. Оставим рейтинг какой был или установим -1 (нет рейтинга), если до этого не было рейтинга
		else
			$rating = $rating ?: '-1';

		// обновим метаполя
		update_post_meta( $post_id, 'kinopoisk_rating_uptime', time() );
		update_post_meta( $post_id, 'kinopoisk_rating', $rating );
	}

	return $rating;
}

// Функция готова, теперь на странице, где нужно вывести рейтинг вызываем её так:
echo get_film_rating();

Коды ответов

Стандартные коды ответов в для HTTP(s) запросов:

/** @var array Map of standard HTTP status code/reason phrases */
$phrases = [
	100 => 'Continue',
	101 => 'Switching Protocols',
	102 => 'Processing',
	200 => 'OK',
	201 => 'Created',
	202 => 'Accepted',
	203 => 'Non-Authoritative Information',
	204 => 'No Content',
	205 => 'Reset Content',
	206 => 'Partial Content',
	207 => 'Multi-status',
	208 => 'Already Reported',
	300 => 'Multiple Choices',
	301 => 'Moved Permanently',
	302 => 'Found',
	303 => 'See Other',
	304 => 'Not Modified',
	305 => 'Use Proxy',
	306 => 'Switch Proxy',
	307 => 'Temporary Redirect',
	400 => 'Bad Request',
	401 => 'Unauthorized',
	402 => 'Payment Required',
	403 => 'Forbidden',
	404 => 'Not Found',
	405 => 'Method Not Allowed',
	406 => 'Not Acceptable',
	407 => 'Proxy Authentication Required',
	408 => 'Request Time-out',
	409 => 'Conflict',
	410 => 'Gone',
	411 => 'Length Required',
	412 => 'Precondition Failed',
	413 => 'Request Entity Too Large',
	414 => 'Request-URI Too Large',
	415 => 'Unsupported Media Type',
	416 => 'Requested range not satisfiable',
	417 => 'Expectation Failed',
	418 => 'I\'m a teapot',
	422 => 'Unprocessable Entity',
	423 => 'Locked',
	424 => 'Failed Dependency',
	425 => 'Unordered Collection',
	426 => 'Upgrade Required',
	428 => 'Precondition Required',
	429 => 'Too Many Requests',
	431 => 'Request Header Fields Too Large',
	451 => 'Unavailable For Legal Reasons',
	500 => 'Internal Server Error',
	501 => 'Not Implemented',
	502 => 'Bad Gateway',
	503 => 'Service Unavailable',
	504 => 'Gateway Time-out',
	505 => 'HTTP Version not supported',
	506 => 'Variant Also Negotiates',
	507 => 'Insufficient Storage',
	508 => 'Loop Detected',
	511 => 'Network Authentication Required',
];
6 комментариев
    Войти