Рубрика «PHP»

Что такое кэширование?

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

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

Единственная проблема — это устаревание кэша. Допустим, что страница изменились, а кэш страницы — еще нет, и пользователю будет выдаваться старая версия. Способы борьбы:

  1. Выставлять более-менее приемлимое время устаревания кэша. Например, через 10 минут страница устаревает и кэш генерится заново.
  2. Сделать в административной части сайта кнопку «очистить кэш». В некоторых CMS вообще нет кнопки «очистить кэш», вместо нее есть кнопка «перегенерить сайт целиком». Нажимаем на эту кнопку — и весь сайт генерится в статичные файлы, то есть, фактически, в кэш.
  3. «Перегенерить сайт целиком» — ресурсоемкая операция. Гораздо лучше при редактировании страницы удалять кэш только той страницы, которую мы изменили. Но тут есть сложность: часто изменение одной страницы затрагивает и несколько других. Главное — понять, каких именно и очистить кэш у них тоже.

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

<?php
// раздел настроек, которые вы можете менять
$cachedir = $_SERVER['DOCUMENT_ROOT'].'/cache/';
$cachetime = 3600; //время жизни кэша (1 час)

$thispage = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$cachelink = $cachedir.md5($thispage).".html";
if (file_exists($cachelink)) {
    $cachelink_time = filemtime($cachelink);
    // если кэш еще не устарел, читаем страницу из файла и отдаем браузеру
    if ((time() - $cachetime) < $cachelink_time) {
        readfile($cachelink);
        die();
    }
}
ob_start();
?>

А в конце скрипта добавим такие строчки

<?php
$fp = fopen($cachelink, 'w');
fwrite($fp, ob_get_contents());
fclose($fp);
ob_end_flush();
?>

Вот, собственно, и все. Все ваши страницы будут кэшироваться на 3600 секунд. Кэш страниц будет сохранен в папке cache.

Функции, которые мы использовали:

void ob_start()
Эта функция включает буферизацию вывода. Если буферизация вывода активна, никакой вывод скрипта не высылается (кроме headers); вывод сохраняется во внутреннем буфере. Содержимое этого внутреннего буфера может быть скопировано в строковую переменную с использованием ob_get_contents(). Для вывода содержимого этого внутреннего буфера используйте ob_end_flush(). Альтернативно ob_end_clean() втихую отбрасывает содержимое буфера.

string ob_get_contents()
Возвращает содержимое буфера вывода или FALSE, если буферизация вывода неактивна.

void ob_end_flush()
Эта функция отправляет содержимое буфера вывода (если оно имеется) и выключает буферизацию вывода. Если вы хотите в дальнейшем обработать содержимое буфера, вы должны вызвать ob_get_contents() до ob_end_flush(), так как содержимое буфера выбрасывается после вызова ob_end_flush().

Ссылки по теме:

Как узнать сколько раз скачали файл?

Допустим, у вас на сайте есть раздел Downloads, где посетитель может скачать скрипты, музыку, фотографии и т.п. Но как узнать, какие файлы пользуются успехом, а какие лежат мертвым грузом (и их можно безболезненно удалить, чтобы не занимали место)?

Как решить эту проблему? Выход - счетчик скачиваний. Вы наверняка уже их видели. Обычно этот счетчик устанавливается рядом со ссылкой на скачиваемый документ. Примерно, все выглядит так:

Ссылка – Скачали [57]

57 – это число, которое увеличивается на 1 при каждом скачивании файла.

Пусть информация о файлах для скачивания у нас хранится в таблице базы данных, которая имеет следующую структуру:

  • id - уникальный ID файла
  • title - название программы, например, “Текстовой редактор NotePad++
  • about - краткое описание программы, например, “Бесплатный редактор текстовых файлов (замена стандартного Блокнота) с поддержкой синтаксиса большого количества языков программирования, ориентирован для работы в операционной системе MS Windows
  • name - имя файла для скачивания, например, NotePadPP.zip
  • download - количество скачиваний

Вообще говоря, файлы можно просто хранить под именами 1.zip, 2.zip, 3.zip, где 1, 2, 3 - уникальные ID. Потому как в поле name особого смысла нет - это избыточная информация. Единственная польза от такого поля - это при скачивании отдавать файл с каким-нибудь осмысленным именем, т.е. NotePadPP.zip, а не 1.zip.

Все файлы для скачивания лежат в директории
DOCUMENT_ROOT/downloads
В этой же директории лежит файл index.php, который выводит список всех файлов, доступных для скачивания и файл download.php, который отдает файлы на скачивание.

Файл index.php

<?php
$query = "SELECT id, name, about, download FROM programs WHERE 1";
$res = mysql_query( $query );
echo '<table border="1">';
while( $row = mysql_fetch_array( $res ) ) {
  echo '<tr>';
  echo '<td>'.$row['name'].'</td>';
  echo '<td>'.$row['about'].'</td>';
  echo '<td><a href="/downloads/download.php?id='.$row['id'].'" target="_blank">Скачать</td>';
  echo '<td>Скачан '.$row['downloads'].' раз</td>';
  echo '</tr>';
}
echo '</table>';
?>

Файл download.php

<?php
if ( !isset( $_GET['id'] ) ) {
  // если не передан ID файла
  header ("HTTP/1.0 404 Not Found");
  die();
}
$id = (int)$_GET['id'];
if ( $id < 1 ) {
  header ("HTTP/1.0 404 Not Found");
  die();
}
// Узнаем имя файла для скачивания
$query = "SELECT name FROM programs WHERE id=".$id;
$res = mysql_query( $query );
if ( mysql_num_rows( $res ) == 0 ) {
  header ("HTTP/1.0 404 Not Found");
  die();
}
$filename = mysql_result( $res, 0, 0 );
// если файла нет
if (!file_exists($filename)) {
  header ("HTTP/1.0 404 Not Found");
  die();
}
// сообщаем размер файла
header( 'Content-Length: '.filesize($filename) );
// дата модификации файла для кеширования
header( 'Last-Modified: '.date("D, d M Y H:i:s T", filemtime($filename)) );
// сообщаем тип данных - zip-архив
header('Content-type: application/zip');
// файл будет получен с именем $filename
header('Content-Disposition: attachment; filename="'.$filename.'"');
// начинаем передачу содержимого файла
readfile($filename);
// Увеличиваем счетчик количества закачек
mysql_query( "UPDATE programs SET download=download+1 WHERE id=".$id );
?>

Отправка сообщения с сайта на e-mail

В очередной раз встретил на форуме сообщение: “Скажите, как сделать скрипт на php для отправки на мыло через сайт. Например, я ввожу на сайте в поля нужный текст и нажимаю “Отправить”. После чего текст отправляется кому-нибудь на e-mail. Очень нужно!“. Быстро набросал простенький скрипт отправки сообщений на e-mail. Когда в следующий раз услышу такой вопрос - буду давать ссылку на эту страницу.

Для отправки почтового сообщения создадим HTML-форму, состоящую из трех текстовых полей для имени автора name, его e-mail адреса email, темы сообщения subject и текстовой области message для ввода содержимого письма.

Письмо будем отправлять по адресу администратора сайта $admin = ‘admin@mail.ru’

<?php
session_start();
$admin = 'admin@mail.ru';

if ( isset( $_POST['sendMail'] ) ) {
  $name  = substr( $_POST['name'], 0, 64 );
  $email   = substr( $_POST['email'], 0, 64 );
  $subject = substr( $_POST['subject'], 0, 64 );
  $message = substr( $_POST['message'], 0, 250 );
 
  $error = '';
  if ( empty( $name ) ) $error = $error.'<li>Не заполнено поле "Имя"</li>';
  if ( empty( $email ) ) $error = $error.'<li>Не заполнено поле "E-mail"</li>';
  if ( empty( $subject ) ) $error = $error.'<li>Не заполнено поле "Тема"</li>';
  if ( empty( $message ) ) $error = $error.'<li>Не заполнено поле "Сообщение"</li>';
  if ( !empty( $email ) and !preg_match( "#^[0-9a-z_\-\.]+@[0-9a-z\-\.]+\.[a-z]{2,6}$#i", $email ) )
    $error = $error.'<li>поле "E-mail" должно соответствовать формату somebody@somewhere.ru</li>';
  if ( !empty( $error ) ) {
    $_SESSION['sendMailForm']['error']   = '<p>При заполнении формы были допущены ошибки:</p><ul>'.$error.'</ul>';
    $_SESSION['sendMailForm']['name']    = $name;
    $_SESSION['sendMailForm']['email']   = $email;
    $_SESSION['sendMailForm']['subject'] = $subject;
    $_SESSION['sendMailForm']['message'] = $message;
    header( 'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }
 
  $body = "АВТОР:\r\n".$name."\r\n\r\n";
  $body .= "E-MAIL:\r\n".$email."\r\n\r\n";
  $body .= "ТЕМА:\r\n".$subject."\r\n\r\n";
  $body .= "СООБЩЕНИЕ:\r\n".$message;
  $body = quoted_printable_encode( $body );

  $theme   = '=?windows-1251?B?'.base64_encode('Заполнена форма на сайте').'?=';
  $headers = "From: ".$_SERVER['SERVER_NAME']." <".$email.">\r\n";
  $headers = $headers."Return-path: <".$email.">\r\n";
  $headers = $headers."Content-type: text/plain; charset=\"windows-1251\"\r\n";
  $headers = $headers."Content-Transfer-Encoding: quoted-printable\r\n\r\n";
 
  if ( mail($admin, $theme, $body, $headers) )
    $_SESSION['success'] = true;
  else
    $_SESSION['success'] = false;
  header( 'Location: '.$_SERVER['PHP_SELF'] );
  die();
}
 
function quoted_printable_encode ( $string ) {
   // rule #2, #3 (leaves space and tab characters in tact)
   $string = preg_replace_callback (
   '/[^\x21-\x3C\x3E-\x7E\x09\x20]/',
   'quoted_printable_encode_character',
   $string
   );
   $newline = "=\r\n"; // '=' + CRLF (rule #4)
   // make sure the splitting of lines does not interfere with escaped characters
   // (chunk_split fails here)
   $string = preg_replace ( '/(.{73}[^=]{0,3})/', '$1'.$newline, $string);
   return $string;
}

function quoted_printable_encode_character ( $matches ) {
   $character = $matches[0];
   return sprintf ( '=%02x', ord ( $character ) );
}
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Отправить письмо</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>

<?php
if ( isset( $_SESSION['success'] ) ) {
  if ( $_SESSION['success'] )
    echo '<p>Письмо успешно отправлено</p>';
  else
    echo '<p>Ошибка при отправке письма</p>';
  unset( $_SESSION['success'] );
}
if ( isset( $_SESSION['sendMailForm'] ) ) {
  echo $_SESSION['sendMailForm']['error'];
  $name    = htmlspecialchars ( $_SESSION['sendMailForm']['name'] );
  $email   = htmlspecialchars ( $_SESSION['sendMailForm']['email'] );
  $subject = htmlspecialchars ( $_SESSION['sendMailForm']['subject'] );
  $message = htmlspecialchars ( $_SESSION['sendMailForm']['message'] );
  unset( $_SESSION['sendMailForm'] );
} else {
  $name    = '';
  $email   = '';
  $subject = '';
  $message = '';
}
?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST">
<table>
<tr><td>Имя:</td><td><input type="text" name="name" maxlength="64" value="<?php echo $name ?>" /></td></tr>
<tr><td>E-mail:</td><td><input type="text" name="email" maxlength="64" value="<?php echo $email ?>" /></td></tr>
<tr><td>Тема:</td><td><input type="text" name="subject" maxlength="64" value="<?php echo $subject ?>" /></td></tr>
<tr><td>Сообщение:</td><td><textarea name="message" rows="5" cols="30"><?php echo $message ?></textarea></td></tr>
<tr><td>&nbsp;</td><td><input type="submit" name="sendMail" value="Отправить" /></td></tr>
</table>
</form>

</body>
</html>

Чтобы понять, зачем здесь сессия и редирект после вызова функции mail(), надо прочитать статью Обработка ошибочного заполнения формы.

Попробуйте убрать редирект и поработать со скриптом. Во-первых, если после отправки формы нажать Refresh, то браузер выдаст сообщение о том, что страницу обновить невозможно без повторной отсылки данных. Во-вторых, если после отправки сообщения уйти на другую страницу, то при нажатии на кнопку Back (т.е. при попытке вернуться на POST-страницу) опять будет выведено сообщение о необходимости повторной отсылки данных. Мало того, что это совершенно нелогично и неудобно с точки зрения пользователя (он ведь уже отправил данные!), так ещё и если он в этот момент нажмёт “OK”, то данные формы будут отправлены повторно. Соответственно, и письмо будет отправлено еще раз.

Проблем этих можно избежать, если после обработки POST-запроса сразу же делать GET-редирект.

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

Ссылки по теме: