Welcome to php club

PHP FAQ from PHPclub.ru: cahcing/CaptureServerSideOutputForCaching ...

Начало | Каталог | Изменения | НовыеКомментарии | Вам запрещён доступПользователи | Вам запрещён доступРегистрация | Вход:  Пароль:  

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

Как я могу захватить данные на стороне сервера для кэширования?

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

Несколько слов о кэшировании при помощи шаблонов


Шаблонные движки типа Smarty часто говорят о кэшировании шаблонов. Обычно эти движки предлагают встроенный механизм для сохранения откомпилированной версии шаблона (т.е. генерируют из шаблона PHP-исходник), что предохраняет нас от необходимости парсить шаблон каждый раз, когда запрашивается страница. Это не нужно путать с кэшированием вывода, которое имеет отношение к кэшированию предоставляемого HTML (или другого вывода), который посылает PHP в браузер. Вы можете успешно использовать оба типа кэширования одновременно на одном и том же сайте.


Сейчас мы рассмотрим встроенный механизм кэширования на PHP, использующий буферизацию вывода, который может использоваться вами независимо от способа создания контента (с шаблонами или без шаблонов). Рассмотрим ситуацию в которой ваш скрипт отображает результат использую, к примеру, echo или print, чтобы выдать данные непосредственно в браузер. В таком случае вы можете использовать функции управления выводом PHP для хранения данных в буферной памяти, над которой ваш PHP-скрипт имеет и доступ, и контроль.


Вот простой пример:


Пример 5.1. 1.php

<?php 
  
// Начинаем буферизацию вывода
  
ob_start();    

  
// Выводим некоторый текст (который сохраняется в буфере);
  
echo '1. Выводим это в буфер<br />'

  
// Получаем содержимое буфера
  
$buffer ob_get_contents(); 

  
// Останавливаем буферизацию и очищаем буфер вывода
  
ob_end_clean(); 

  
// Выводим некоторый текст обычным образом
  
echo '2. Нормальный вывод<br />'

  
// Вывод содержимого буфера
  
echo $buffer
?>

Сам буфер хранит вывод как строку. Так, в вышеприведённом скрипте мы начинаем буферизацию с ob_start и используем echo, чтобы вывести что-либо. Затем мы используем ob_get_contents, чтобы выбрать данные, помещённые в буфер оператором echo, и сохранить их в строке. Функция ob_end_clean останавливает буферизацию вывода и уничтожает его содержимое, как альтернативу можно использовать ob_end_flush, чтобы вывести содержимое буфера.


Вышеописанный скрипт выведет:

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

Заголовки HTTP и буферизация вывода


Буферизация вывода может помочь решить наиболее общую проблему, связанную с функцией header, не говоря уже о session_start и set_cookie. Обычно, если вы вызываете любую из этих функций после того, как начался вывод страницы, вы получите противное сообщение об ошибке. При включенной буферизации вывода единственным типом вывода, избегающим буферизации, являются HTTP-заголовки. Используя ob_start в самом начале выполнения вашего приложения, вы можете посылать заголовки в любой понравившейся точке программы, не сталкиваясь с обычными ошибками. Затем, как только вы будете уверены, что больше выводить HTTP-заголовки не потребуется, вы можете сразу же вывести содержимое страницы из буфера.
(прим. переводчика: следует заметить что подобное использование данной функции крайне неоправдано. В большинстве случаев необходимости в использовании буферизации вывода для избавления ошибок указанного типа просто не существует и всё с лёгкостью может быть исправлено правильным проектированием приложения)


Использование буферизации вывода для кэширования на стороне сервера


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


Пример 5.2. 2.php

<?php 
  
// Если существует кэшированная версия…
  
if (file_exists('./cache/2.cache')) { 
    
// Читаем и выводим файл
    
readfile('./cache/2.cache'); 
    exit(); 
  } 

  
// Начинаем буферизацию вывода
  
ob_start(); 

  
// Выводим остальной HTML
?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>Кэшированная страница</title> 
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /> 
</head> 
<body> 
Эта страница кэшируется средствами PHP
<a href="http://www.php.net/outcontrol">Функции управления выводом</a>
</body> 
</html> 

<?php 
  
// Получаем содержимое буфера
  
$buffer ob_get_contents();

  
// Останов буферирования и вывод буфера
  
ob_end_flush(); 

  
// Сохранение кэш-файла с контентом
  
$fp fopen('./cache/2.cache''w'); 
  
fwrite($fp$buffer); 
  
fclose($fp); 
?>

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


Файл 2.cache содержит точную копию HTML, которую предоставляет скрипт:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>Кэшированная страница</title> 
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /> 
</head> 
<body> 
Эта страница кэшируется средствами PHP
<a href="http://www.php.net/outcontrol">Функции управления выводом</a> 
</body> 
</html>

Блочная буферизация


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


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


Вот пример, демонстрирующий этот принцип:


Пример 5.3. 3.php (начало)

<?php 
  
/** 
  * Запись кэш-файла
  * @param string contents – содержание буфера
  * @param string filename – имя файла, используемое при создании кэш-файла
  * @return void 
  */ 
  
function writeCache($content$filename) { 
    
$fp fopen('./cache/' $filename'w'); 
    
fwrite($fp$content); 
    
fclose($fp); 
  } 

  
/** 
  * Проверка кэш-файлов
  * @param string filename – имя проверяемого кэш-файла
  * @param int expiry – максимальный «возраст» файла в секундах
  * @return mixed содержимое кэша или false 
  */ 
  
function readCache($filename$expiry) {
    if (
file_exists('./cache/' $filename)) {
      if ((
time() - $expiry) > filemtime('./cache/' $filename))
        return 
FALSE;
      
$cache file('./cache/' $filename);
      return 
implode(''$cache);
    }
    return 
FALSE
  }
?>

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


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


Давайте продолжим этот пример. После того, как запущена буферизация вывода, начинается обработка. Сначала скрипт вызывает readCache, чтобы узнать, существует ли файл 3_header.cache, он содержит шапку страницы – заголовок HTML и начало тела. Мы используем функцию date PHP чтобы вывести время, когда страница фактически была сгенерирована, таким образом вы увидите различные кэш-файлы в работе, когда страница будет отображена.


Пример 5.4. 3.php (продолжение)

<?php
  
// Начинаем буферизацию вывода
  
ob_start(); 

  
// Обработка шапки
  
if (!$header readCache('3_header.cache'604800)) { 
    
// Вывод шапки
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>Страница, кэшированная поблочно </title> 
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /> 
</head> 
<body> 
Время создания шапки: <?php echo date('H:i:s'); ?><br /> 

<?php 
    $header 
ob_get_contents(); 
    
ob_clean(); 
    
writeCache($header,'3_header.cache'); 
  }
?>

Что же случается когда кэш-файл не найден? Выводится некоторый контент и присваивается переменной при помощи ob_get_contents, после чего буфер очищается функцией ob_clean. Это позволяет нам перехватывать вывод по частям и сопоставлять их с индивидуальными кэш-файлами при помощи writeCache. Заголовок страницы теперь хранится как файл, который может быть использован без нашего вмешательства в пересборку страницы. Давайте вернёмся на секунду к началу условного оператора. Когда мы вызывали readCache, мы передали ей время жизни кэша в 604800 секунд (одна неделя), readCache использует время модификации кэш-файла, чтобы определить, является ли кэш-файл всё ещё допустимым.


Для содержимого (тела) страницы мы по прежнему будем использовать тот же процесс. Однако на сей раз при вызове readCache мы будем использовать время жизни кэша в пять секунд, кэш-файл будет модифицироваться каждый раз, когда он «старше» 5 секунд:


Пример 5.5. 3.php (продолжение)

<?php
  
// Обработка тела страницы
  
if (!$body readCache('3_body.cache'5)) { 
    echo 
'Время создания тела: ' date('H:i:s') . '<br />'
    
$body ob_get_contents(); 
    
ob_clean(); 
    
writeCache($body'3_body.cache'); 
  }
?>

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


Пример 5.6. 3.php (окончание)

<?php
// Обработка нижнего колонтитула страницы
  
if (!$footer readCache('3_footer.cache'604800)) { 
?> 

Время создания нижнего колонтитула: <?php echo date('H:i:s'); ?><br /> 
</body> 
</html> 

<?php 
    $footer 
ob_get_contents(); 
    
ob_clean(); 
    
writeCache($footer'3_footer.cache'); 
  }
  
// останавливаем буферизацию 
  
ob_end_clean(); 

  
// Выводим содержимое страницы
  
echo $header $body $footer
?>

Конечный результат выглядит примерно так:
Время создания шапки: 17:10:42 
Время создания тела: 18:07:40 
Время создания нижнего колонтитула: 17:10:42

Заголовок и нижний колонтитул обновляются еженедельно, в время как тело модифицируется, когда оно старее 5 секунд.


Блок-схема на рисунке 5.1 суммирует методологию блочной буферизации.

Рисунок 5.1. Блок-схема блочной буферизации вывода

Вложенные буферы


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


На предыдущую страницу | На следующую страницу


 
Комментариев нет. [Показать комментарии/форму]