Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6286
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

464

Часть IV. Стандартные функции PHP

почтовом клиенте нужную кодировку. Этот раздел призван решить проблему коди-

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

Заголовок Content-type и кодировка

Сначала давайте договоримся об одном соглашении: будем использовать только функцию mailx(), приведенную в листинге 27.3, но не mail(). Это позволит нам

перекодировать письмо целиком "одним махом", включая как заголовки, так и тело.

Обратите внимание на заголовок Content-type, который мы так старательно указыва-

ли в предыдущих примерах. Он задает, что, во-первых, письмо доставляется как простой текст (text/plain), а во-вторых, что его кодировка — windows-1251. Таким

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

Кодировка заголовков

Заголовок Content-type задает кодировку тела письма. Большинство почтовых про-

грамм также используют его для того, чтобы определять и кодировку заголовков (таких, например, как Subject, From и To). К сожалению, существуют программы, которые этого не делают.

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

закодировать.

Существует несколько разных способов кодирования заголовков, но мы рассмотрим

лишь самый популярный из них — base64. С его использованием следующая строка

в кодировке Windows-1251

Пупкин Василий Артемьевич

превратится в такой вид:

=?windows-1251?B?z/Pv6ujtIMLg8ejr6OkgwPDy5ez85eLo9w==?=

Нетрудно заметить общий принцип кодирования:

=?кодировка?способ?код?=

Здесь

кодировка — имя кодировки текста (например, windows-1251, koi8-r и т. д.);

способ — метод кодирования (в нашем случае B — base64);

код — закодированное представление строки, которое возвращает функция PHP base64_encode().

Закодированных участков в строке, начинающихся с =? и заканчивающихся ?=, мо-

жет быть сколько угодно. Дело осложняется тем, что адреса электронной почты не

Глава 27. Посылка писем через PHP

465

должны быть закодированы. Поэтому мы не можем взять каждый заголовок и зако-

дировать его от начала и до конца — нужно быть более избирательными.

В листинге 27.5 приведена функция mailenc(), которая base64-кодирует в каждом заголовке письма последовательности символов, начинающиеся с недопустимого по стандарту символа. В своей работе функция использует еще две подпрограммы, также приведенные в листинге.

Листинг 27.5. Файл lib/mailenc.php

<?php ## Кодирование заголовков письма.

//Корректно кодирует все заголовки в письме $mail с использованием

//метода base64. Кодировка письма определяется автоматически на основе

//заголовка Content-type. Возвращает полученное письмо.

function mailenc($mail) {

// Разделяем тело сообщения и заголовки.

list ($head, $body) = preg_split("/\r?\n\r?\n/s", $mail, 2);

//Определяем кодировку письма по заголовку Content-type. $encoding = '';

$re = '/^Content-type:\s*\S+\s*;\s*charset\s*=\s*(\S+)/mi'; if (preg_match($re, $head, $p)) $encoding = $p[1];

//Проходимся по всем строкам-заголовкам.

$newhead = "";

foreach (preg_split('/\r?\n/s', $head) as $line) { // Кодируем очередной заголовок.

$line = mailenc_header($line, $encoding); $newhead .= "$line\r\n";

}

// Формируем окончательный результат. return "$newhead\r\n$body";

}

//Кодирует в строке максимально возможную последовательность

//символов, начинающуюся с недопустимого символа и НЕ

//включающую e-mail (адреса e-mail обрамляют символами < и >).

//Если в строке нет ни одного недопустимого символа, преобразование

//не производится.

function mailenc_header($header, $encoding) {

//Кодировка не задана — делать нечего. if (!$encoding) return $header;

//Сохраняем кодировку в глобальной переменной. Без использования

//ООП это — единственный способ передать дополнительный параметр

//callback-функции.

$GLOBALS['mail_enc_header_encoding'] = $encoding;

return preg_replace_callback(

'/([\x7F-\xFF][^<>\r\n]*)/s',

'mailenc_header_callback',

$header

);

}

466

Часть IV. Стандартные функции PHP

// Служебная функция для использования в preg_replace_callback(). function mailenc_header_callback($p) {

$encoding = $GLOBALS['mail_enc_header_encoding']; // Пробелы в конце оставляем незакодированными. preg_match('/^(.*?)(\s*)$/s', $p[1], $sp);

return "=?$encoding?B?".base64_encode($sp[1])."?=".$sp[2];

}

?>

С помощью данной функции мы можем написать окончательный вариант скрипта

для отсылки почты (листинг 27.6).

Листинг 27.6. Файл mail_enc.php

<?php ## Отправка почты по шаблону (наилучший способ). // Подключаем функции.

include_once "lib/mailx.php"; include_once "lib/mailenc.php";

$text = "Well, now, ain't this a surprise?";

$tos = array("Пупкин Василий <poupkinne@mail.ru>, Иванов <b@mail.ru>"); $tpl = file_get_contents("mail.eml");

foreach ($tos as $to) { $mail = $tpl;

$mail = strtr($mail, array( "{TO}" => $to, "{TEXT}" => $text,

));

$mail = mailenc($mail); mailx($mail);

}

?>

Кодирование тела письма

Вообще говоря, символы с кодом, превышающим 127, по стандарту недопустимы не

только в заголовках, но также и в теле письма. Их необходимо кодировать. Тем не

менее чаще всего данным правилом пренебрегают. Автор этих строк специально просмотрел свой почтовый ящик в поисках писем, текст которых шел бы не "откры-

тым текстом", а в закодированном виде, и обнаружил, что их практически нет: даже HTML-письма приходят в виде обычного текста с Content-type, равным text/html.

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

Content-Transfer-Encoding: base64

Обычно кодируют только бинарные вложения: изображения, архивы и другие файлы. Текст и HTML чаще всего идет как есть.

Глава 27. Посылка писем через PHP

467

Письма с вложениями

Один из самых популярных вопросов, касающихся работы с почтой в PHP, звучит так: "Можно ли отправлять письма с вложениями — файлами, картинками и т. д.?".

Ответ на него — положительный. С точки зрения функции mail() письмо с вложениями ничем не отличается от "обычного" сообщения — это точно такой же блок

текстовых данных.

Полноценная работа с вложениями довольно сложна, и в то же время весьма моно-

тонна, поэтому мы не будем приводить в книге никаких исходных кодов на эту тему.

Существует очень много готовых функций и библиотек, предназначенных для отправки писем с вложениями. Самые простые функции можно найти прямо в документации PHP по адресу http://php.net/mail (см. пользовательские комментарии). Мы также можем порекомендовать вам присмотреться к мощной библиотеке по адресу http://phpmailer.sourceforge.net, имеющей массу возможностей по отправке писем.

Динамическая смена кодировки

До сих пор мы отправляли почту в той же самой кодировке, что установлена по умолчанию на сервере (например, Windows-1251). В некоторых случаях желательно перед отправкой сменить кодировку. Например, раньше считалось "хорошим тоном" кодировать все письма с использованием KOI8-R. (Впрочем, в современном мире необходимость в перекодировании писем перед отправкой практически исчезла, т. к. все почтовые программы "понимают" самые разнообразные кодировки. Главное,

чтобы был указан корректный заголовок Content-type.)

В PHP существует специальная функция, занимающаяся преобразованиями одной

русской кодировки в другую. Она называется convert_cyr_string().

Объем информации в русском Интернете (или, как иногда говорят, Рунете) составляет едва ли больше нескольких процентов от всего Интернета. Да и по количеству людей Россия примерно в 10 раз уступает Китаю, в 7 — Индии и в 2 — США. Откровенно говоря, Рунет — это всего лишь капля в море, и почему разработчики решили добавить в ядро PHP специальную функцию для работы с кириллицей, не совсем понятно.

string convert_cyr_string(string $str, string $from, string $to)

Функция переводит строку $str из кодировки $from в кодировку $to. Это имеет

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

k — KOI8-R;

w — Windows-1251;

i — ISO8859-5;

468

Часть IV. Стандартные функции PHP

a — X-CP866;

d — X-CP866;

m — X-MAC-Cyrillic.

Применив функцию для письма, не забудьте подправить и заголовок Content-type. В противном случае письмо окажется полностью нечитаемым: ведь кодировка, указанная в заголовке, не совпадет с его настоящей кодировкой.

Активные шаблоны

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

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

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

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

ным приемом:

перехватываем выходной поток письма при помощи ob_start();

запускаем шаблон письма вызовом include(), как будто бы обычную программу на PHP;

получаем текст, который был выведен операторами echo в шаблоне — ob_get_contents(), а затем завершаем перехват выходного потока функцией ob_end_clean(), не допуская его вывод в браузер.

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

Листинг 27.7 иллюстрирует данный алгоритм.

Листинг 27.7. Файл lib/template.php

<?php ## Обработка шаблона.

//Функция делает то же самое, что инструкция include, однако

//блокирует вывод текста в браузер. Вместо этого текст возвращается

//в качестве результата. Функцию можно использовать, например,

//для обработки почтовых шаблонов.

function template($__fname, $vars) {

Глава 27. Посылка писем через PHP

469

//Перехватываем выходной поток. ob_start();

//Запускаем файл как программу на PHP. extract($vars, EXTR_OVERWRITE); include($__fname);

//Получаем перехваченный текст.

$text = ob_get_contents(); ob_end_clean();

return $text;

}

?>

Как видите, мы используем функцию extract(), превращающую в переменные элементы массива для более легкого доступа к ним. Этим способом мы можем явно указать, какие переменные доступны шаблону напрямую, а какие — нет.

С использованием данной библиотеки мы могли бы переписать наш сценарий отправки почты так, как показано в листинге 27.8.

Листинг 27.8. Файл mail_php.php

<?php ## Отправка почты с использованием активного шаблона. // Подключаем функции.

include_once "lib/mailx.php"; include_once "lib/mailenc.php"; include_once "lib/template.php";

$text = "Well, now, ain't this a surprise?";

$tos = array("Пупкин Василий <poupkinne@mail.ru>"); $a = 1;

foreach ($tos as $to) {

// "Разворачиваем" шаблон, передавая ему $to и $text. $mail = template("mail.php.eml", array(

"to" => $to, "text" => $text,

)); // Дальше как обычно: кодируем и отправляем.

$mail = mailenc($mail); mailx($mail);

}

?>

Шаблон же теперь будет выглядеть следующим образом (листинг 27.9).

Листинг 27.9. Файл mail.php.eml

From: Почтовый робот <somebody@mail.ru>

To: <?=$to?>

Subject: Добрый день!

Content-type: text/plain;

charset=windows-1251

470

Часть IV. Стандартные функции PHP

Привет, <?=$to?>!

<?=$text?>

Содержимое переменных окружения на момент отправки письма:

<?print_r($_SERVER)?>

Это сообщение сгенерировано роботом — не отвечайте на него.

Учтите, что в PHP все символы переводов строки и табуляции, расположенные после тега ?>, удаляются! К счастью, данный факт не касается пробелов. Это значит, что вы должны явно указать после ?> хотя бы один пробел (или любой другой символ, отличный от возврата каретки), если не хотите, чтобы следующая строка "приклеилась" к предыдущей. В листинге этих пробелов не видно, но на самом деле они там есть. Еще раз: если пропустить пробел после <?=$to?>, то поле Subject "приклеится в хвост" полю To, и получится, конечно же, совсем не то, на что вы рассчитывали.

Настройки PHP

В PHP существуют две взаимоисключающих директивы для настройки поведения функции mail(). Они задаются в файле php.ini, а одна из них может быть изменена и при помощи функции ini_set() (см. гл. 23). Давайте рассмотрим директивы подробнее.

sendmail_path

Возможные значения: путь к исполняемому файлу.

Где устанавливается: php.ini.

Данная настройка имеет наивысший приоритет. Она определяет путь к программе sendmail, которая в Unix традиционно занимается доставкой почты адресату. Вопре-

ки уверениям разработчиков PHP, данная директива прекрасно работает не только в Unix, но и в Windows (что позволяет, например, использовать "почтовую заглушку" в

комплексе Денвер (см. гл. 6) вместо отправки почты; это неоценимо при отладке сценариев).

SMTP

Возможные значения: имя SMTP-сервера провайдера.

Где устанавливается: php.ini, .htaccess, ini_set().

В Windows имеется возможность отправлять почту не через sendmail, а с использо-

ванием SMTP-сервера провайдера (почтового сервера, который вы обычно указываете в настройках своей почтовой программы). Необходимо заметить, что PHP не

поддерживает SMTP-авторизацию, а значит, отправка почты возможна только на сервер, который явно вам это разрешит (например, на сервер провайдера).

Указать вместо SMTP-сервера сторонний хост, вроде mail.ru, не получится! В противном случае спам наводнил бы Интернет в масштабе, в сотни раз превышающий современный. Впрочем, если вы посылаете письмо как раз на mail.ru, указание его адреса

Глава 27. Посылка писем через PHP

471

в директиве SMTP допустимо — все серверы беспрепятственно принимают "свою" почту и "отказываются" принимать чужую.

Заметьте, что при использовании sendmail_path директива SMTP игнорируется, что бы

она ни содержала.

Что делать, если у вас нет провайдера, предоставляющего почтовый сервер для отсылки писем? В этом случае можно написать функцию для работы с SMTP-сервером через сокеты. В архиве с исходными кодами программ, расположенном на сайте книги, имеется простейший пример такой функции. См. файл с именем mail_manual.php (пример использования) и соответствующую библиотеку lib/socketmail.php.

Ссылки

Приведем некоторые ссылки:

пользовательские комментарии (включая функции для отправки писем с вложениями): http://php.net/mail;

библиотека для расширенной отправки писем: http://phpmailer.sourceforge.net.

Резюме

В данной главе мы научились отправлять электронную почту из скриптов на PHP и

постарались обойти все "подводные камни", которые возникают в этом процессе.

Мы рассмотрели популярный способ работы "почтового" скрипта — использование шаблона письма с последующим base64-кодированием заголовков. Отдельное вни-

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

метра конфигурации PHP, влияющих на отправку почты.

ГЛАВА 28

Работа с СУБД MySQL

Листинги данной главы можно найти в подкаталоге mysql.

База данных — совокупность связанных между собой данных, сохраняемая в двумерных таблицах информационной системы. Программное обеспечение информационной системы, обеспечивающей создание, ведение и совместное использование баз данных, называется системой управления базами данных (СУБД). В этой главе мы рассмотрим функции PHP, предназначенные для работы с одной из самых популярных СУБД — MySQL. В PHP есть функции для "общения" и с другими системами управления базами данных (например, SQLite, Sybase, Oracle и т. д.), но мы остановились именно на MySQL в силу ее простоты и универсальности для большинства приложений. Конечно, прежде чем работать с MySQL, нужно установить соответствующее программное обеспечение — программу-сервер MySQL. Как это сделать в системе Windows, подробно описано в части II настоящей книги.

Данная глава ни в коей мере не претендует на исчерпывающее описание языка SQL и системы управления базами данных MySQL. Здесь приведен только основной минимум материала. Имея его под рукой, можно начинать писать сценарии, использующие MySQL. Если вам понадобится подробная документация, вы сможете найти ее в любом дистрибутиве MySQL.

Что такое база данных?

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

Запись может содержать в себе одно или несколько именованных полей. Число и имена полей задаются при создании таблицы. Каждое поле имеет определенный тип (например, целое число, строка текста, массив символов и т. д.).

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

Глава 28. Работа с СУБД MySQL

473

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

В таблицу всегда можно добавить новую запись. Другая операция, которую часто производят с записью (точнее, с таблицей), — это поиск. Например, запрос поиска может быть таким: "Выдать все записи, в первом поле которых содержится число, меньшее 10, во втором — строка, включающая слово word, а в третьем — не должен быть ноль". Из найденных записей в программу можно извлекать какие-то части данных (или не извлекать), также записи таблицы можно удалить.

Следует еще раз заметить, что обычно все упомянутые операции осуществляются очень быстро. Например, сервер MySQL может менее чем за 0,01 секунды из 10 млн записей выделить ту, у которой значение определенного поля совпадает с нужным числом или строкой. Высокое быстродействие в большей мере обусловлено тем, что данные не просто "свалены в кучу", а определенным образом упорядочены и все время поддерживаются в таком состоянии.

Неудобство работы с файлами

Прежде чем мы займемся базами данных MySQL и их поддержкой в PHP, давайте определимся, для чего вообще в Web-программировании могут понадобиться базы данных? Ответ на этот вопрос не вполне очевиден, особенно для людей, сталкивающихся со "стандартными" базами данных впервые.

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

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

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