Кэширование в PHP

Автор статьи: Harry Fuecks (Перевод: Муллин Сергей (SiMM), Кузьма Феськов)
Сайт Автора: php.russofile.ru
E-mail Автора: kuzma@russofile.ru
Дата публикации: 25.04.2006


Время изменения страницы

Более практично использовать заголовки Last-Modified и If-Modified-Since, доступные в HTTP 1.0. Технически он известно как выполнение условного GET-запроса, вы возвращаете любой контент, основываясь на условии пришедшего заголовка запроса If-Modified-Since.

При использовании этого метода вы должны отправлять заголовок Last-Modified каждый раз, когда обращаются к вашему PHP-скрипту. При следующем запросе страницы браузером, он отправит заголовок If-Modified-Since, содержащий время, по которому ваш скрипт может определить, обновлялась ли страница со времени последнего запроса. Если это не так, ваш скрипт посылает код статуса HTTP 304, чтобы указать, что страница не изменялась, не выводя при этом содержимого страницы.

Простейший пример условного GET довольно мудрёный, довольно удобное средство, чтобы показать как это работает – PEAR::Cache_Lite. Однако не следует считать, что это пример серверного кэширования, это просто предусматривает файл, периодически модифицирующийся.

Вот код:

**Пример 5.16. ##7.php## (начало)**

<?php 
// Подключаем PEAR::Cache_Lite 
require_once 'Cache/Lite.php'; 

// Определяем настройки Cache_Lite 
$options = array( 
 'cacheDir' => './cache/' 
); 

// Инициализируем Cache_Lite 
$cache = new Cache_Lite($options); 

// Некоторые фиктивные данные для хранения
$id = 'MyCache';

// Инициализируем кэш, если страница запрошена впервые
if (!$cache->get($id)) { 
 $cache->save('Dummy', $id); 
} 

// Рандомизатор…
$random = array(0, 1, 1); 
shuffle($random); 

// Произвольное обновление кэша
if ($random[0] == 0) { 
 $cache->save('Dummy', $id); 
} 

// Получаем время последней модификации кэш-файла
$lastModified = filemtime($cache->_file); 

// Выдаём заголовок HTTP Last-Modified
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); 

// Получаем заголовки запроса клиента – только для Apache
$request = getallheaders(); 

if (isset($request['If-Modified-Since'])) { 
 // Разделяем If-Modified-Since (Netscape < v6 отдаёт их неправильно) 
 $modifiedSince = explode(';', $request['If-Modified-Since']); 

 // Преобразуем запрос клиента If-Modified-Since в таймштамп
 $modifiedSince = strtotime($modifiedSince[0]); 
} else { 
 // Устанавливаем время модификации в ноль
 $modifiedSince = 0; 
} 

// Сравниваем время последней модификации контента с кэшем клиента
if ($lastModified <= $modifiedSince) { 
 // Разгружаем канал передачи данных!
 header('HTTP/1.1 304 Not Modified'); 
 exit(); 
} 

echo 'Сейчас ' . gmdate('H:i:s') . ' по Гринвичу<br />'; 
echo '<a href="' . $_SERVER['PHP_SELF'] . '">Обновить</a><br />'; 
?>

Не забудьте пользоваться ссылкой «Обновить» при запуске этого примера (нажатие Refresh’а обычно очищает кэш вашего браузера). Если вы кликните по ссылке неоднократно, в конечном итоге кэш будет изменён, ваш браузер удалит версию из кэша и сохранит в нём новую страницу, предоставленную PHP.

В вышеприведённом примере мы использовали PEAR::Cache_Lite для создания произвольно модифицируемого кэш-файла. Мы устанавливаем время модификации кэш-файла этой строкой:

<?php 
$lastModified = filemtime($cache->_file);
?>

Говоря техническим языком, это хак, поскольку переменная $_file класса PEAR::Cache_Lite должна быть приватной. Тем не менее мы вынуждены её использовать, чтобы получить имя кэш-файла и узнать его время модификации.

Затем, используя время модификации кэш-файла, мы посылаем заголовок Last-Modified. Нам нужно посылать её для каждой предоставляемой страницы, чтобы вынудить браузер посылать нам заголовок If-Modified-Since с каждым запросом.

<?php 
// Выдаём заголовок HTTP Last-Modified
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
?>

Использование функции getallheaders обеспечивает нам получение от PHP всех входящих заголовков в виде массива. Затем мы должны проверить, что заголовок If-Modified-Since действительно существует, если он есть, мы должны обработать специальный случай старых версий Mozilla (нижа 6й версии), который добавлял в конец (отклоняясь от спецификации) дополнительное поле к заголовку If-Modified-Since. Используя функцию PHP strtotime, мы получаем таймштамп даты, переданной нам браузером. Если такого заголовка нет, мы присваиваем таймштампу ноль, вынуждая таким образом PHP отдать посетителю последнюю версию страницы.

<?php 
// Получаем заголовки запроса клиента – только для Apache
$request = getallheaders(); 

if (isset($request['If-Modified-Since'])) { 
  // Разделяем If-Modified-Since (Netscape < v6 отдаёт их неправильно)
   $modifiedSince = explode(';', $request['If-Modified-Since']); 

   // Преобразуем запрос клиента If-Modified-Since в таймштамп
   $modifiedSince = strtotime($modifiedSince[0]); 
} else { 
  // Устанавливаем время модификации в ноль
   $modifiedSince = 0; 
}
?>

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

<?php 
// Сравниваем время последней модификации контента с кэшем клиента
if ($lastModified <= $modifiedSince) { 
 // Разгружаем канал передачи данных! 
 header('HTTP/1.1 304 Not Modified'); 
 exit(); 
}
?>

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

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