Академический Документы
Профессиональный Документы
Культура Документы
438 PHP
ББК 32.973.26-018.1
К73
Котеров, Д. В.
К73 PHP 7 / Д. В. Котеров, И. В. Симдянов. — СПб.: БХВ-Петербург, 2016. —
1088 с.: ил. — (В подлиннике)
ISBN 978-5-9775-3725-4
Рассмотрены основы языка PHP и его рабочего окружения в Windows, Mac OS X
и Linux.
Отражены радикальные изменения в языке PHP, произошедшие с момента вы-
хода предыдущего издания: трейты, пространство имен, анонимные функции, за-
мыкания, элементы строгой типизации, генераторы, встроенный Web-сервер и
многие другие возможности. Приведено описание синтаксиса PHP 7, а также
функций для работы с массивами, файлами, СУБД MySQL, memcached, регуляр-
ными выражениями, графическими примитивами, почтой, сессиями и т. д. Особое
внимание уделено рабочему окружению: сборке PHP-FPM и Web-сервера nginx,
СУБД MySQL, протоколу SSH, виртуальным машинам VirtualBox и менеджеру
виртуальных машин Vagrant. Рассмотрены современные подходы к Web-разра-
ботке, система контроля версий Git, GitHub и другие бесплатные Git-хостинги, но-
вая система распространения программных библиотек и их разработки, сборка
Web-приложений менеджером Composer, стандарты PSR и другие инструменты и
приемы работы современного PHP-сообщества.
В третьем издании добавлены 24 новые главы, остальные главы обновлены или
переработаны.
На сайте издательства находятся исходные коды всех листингов.
Для Web-программистов
УДК 004.438 PHP
ББК 32.973.26-018.1
Предисловие ................................................................................................................... 29
Для кого написана эта книга ......................................................................................................... 29
Исходные коды .............................................................................................................................. 30
Третье издание ............................................................................................................................... 31
Общая структура книги ................................................................................................................. 31
Часть I ..................................................................................................................................... 31
Часть II .................................................................................................................................... 32
Часть III .................................................................................................................................. 32
Часть IV .................................................................................................................................. 32
Часть V.................................................................................................................................... 33
Часть VI .................................................................................................................................. 33
Часть VII ................................................................................................................................. 33
Часть VIII................................................................................................................................ 33
Часть IX .................................................................................................................................. 33
Часть X.................................................................................................................................... 33
Листинги ......................................................................................................................................... 34
Предметный указатель .................................................................................................................. 34
Благодарности от Дмитрия Котерова .......................................................................................... 34
Благодарности от Игоря Симдянова ............................................................................................ 35
Терминология ................................................................................................................................ 52
Сервер ..................................................................................................................................... 52
Узел ......................................................................................................................................... 53
Порт ........................................................................................................................................ 53
Сетевой демон, сервис, служба............................................................................................. 53
Хост ......................................................................................................................................... 54
Виртуальный хост .................................................................................................................. 54
Провайдер ............................................................................................................................... 55
Хостинг-провайдер (хостер) ................................................................................................. 55
Хостинг ................................................................................................................................... 55
Виртуальный сервер .............................................................................................................. 55
Сайт ......................................................................................................................................... 56
HTML-документ..................................................................................................................... 56
Страница (или HTML-страница) .......................................................................................... 56
Скрипт, сценарий ................................................................................................................... 56
Web-программирование ........................................................................................................ 56
Взаимосвязь терминов .................................................................................................................. 57
World Wide Web и URL................................................................................................................. 57
Протокол................................................................................................................................. 58
Имя хоста ................................................................................................................................ 58
Порт ........................................................................................................................................ 58
Путь к странице...................................................................................................................... 59
Резюме ............................................................................................................................................ 59
CGI изнутри
До сих пор рассматривались лишь теоретические аспекты CGI. Мы знаем в общих чер-
тах, как и что передается пользователю сервером, и наоборот. Однако как же все-таки
должна быть устроена CGI-программа (CGI-сценарий), чтобы работать с этой инфор-
мацией? Откуда она ее вообще получает и куда должна выводить, чтобы переслать
текст пользователю?
И это только небольшая часть вопросов, которые пока остаются открытыми. В данной
главе мы постараемся вкратце описать, как же на самом деле должны быть устроены
CGI-сценарии.
Язык C
Каждый программист обязан хотя бы в общих чертах знать, как "на низком уровне"
работает то, что он использует — будь то операционная система или удобный язык-
интерпретатор для написания сценариев (каким является PHP).
Итак, речь пойдет о программировании на C. Мы выбрали его, потому что именно на
этом языке чаще всего пишут сценарии, которым требуется наиболее тесное взаимо-
действие с сервером и максимально критичное быстродействие (базы данных, поиско-
вые системы, системы почтовой рассылки с сотнями тысяч пользователей и др.).
В пользу языка C говорит так же и то, что его компиляторы можно встретить практиче-
ски в любой сколько-нибудь серьезной операционной системе.
Вы не найдете в этой главе ни одной серьезной законченной программы на C (за ис-
ключением разве что самой простой, типа "Hello, world!"). И даже более того: все лис-
тинги программ, представленные в следующих главах, далеки от идеала и могут быть
сделаны более универсальными, но зато и значительно более длинными. Длиной при-
шлось пожертвовать и выбрать простоту.
В данной главе мы попытались описать все основные приемы, которые могут понадо-
биться при программировании сценариев на C (кроме работы с сокетами, это тема для
отдельной книги). По возможности мы не будем привязываться к специфике конкрет-
Глава 3. CGI изнутри 73
Компиляция программ
Вообще, изучая приведенные в данной главе примеры, вы можете и не проверять их на
практике (особенно если у вас еще нет работающего Web-сервера). Все листинги здесь
предназначены лишь для теоретических целей.
Однако, если вы все же решитесь запустить пару-тройку проверочных CGI-скриптов,
написанных на C, знайте, что это несложно. Вначале исходный код необходимо преоб-
разовать в исполняемый файл, т. е. откомпилировать, причем в той операционной сис-
теме, в которой будет запускаться скрипт (исполняемые файлы в разных ОС сильно
различаются).
Далее в главе предполагается, что вы работаете в UNIX-подобной операционной сис-
теме и вам доступен GNU-компилятор gcc. Для компилирования программы достаточ-
но выполнить команду:
gcc script.c –o "script.cgi"
При этом создастся исполняемый файл script.cgi, на который можно в дальнейшем ссы-
латься из HTML-форм (как именно — читайте дальше).
При использовании Windows можно воспользоваться виртуальной машиной (см. гла-
ву 53).
74 Часть I. Основы Web-программирования
З АМЕЧАНИЕ
FastCGI взаимодействует с клиентом через сокеты или TCP/IP-соединение.
Заголовки ответа
Заголовки ответа должны следовать точно в таком же формате, как и заголовки запро-
са, рассмотренные нами в предыдущей главе. А именно, это набор строк (завершаю-
щийся пустой строкой), каждая из которых представляет собой имя заголовка и его
значение, разделенные двоеточием. Наличие пустого заголовка в конце так же можно
интерпретировать, как два стоящих подряд символа перевода строки \n\n. Затем, как
обычно, могут следовать данные ответа, являющиеся документом, который будет ото-
бражен браузером.
или так:
HTTP/1.1 404 Not Found
В первом примере заголовок говорит браузеру, что все в порядке и дальше следует не-
который документ. Во втором примере сообщается, что затребованный файл не был
найден на сервере. Конечно, существует еще множество других кодов ошибок.
Глава 3. CGI изнутри 75
мы заставим сервер послать браузеру код ответа 404, а также выполнить стандартный
обработчик 404-й ошибки, если он назначен директивой Web-сервера. При этом сам
заголовок Status отправлен не будет — он обрабатывается сервером и после этого вы-
резается.
Приведем другие наиболее распространенные заголовки ответа.
Content-type
Формат:
Content-type: mime_тип; charset=utf-8
Задает тип документа и его кодировку. Параметр charset указывает кодировку доку-
мента (в нашем примере это UTF-8). Поле mime_тип определяет тип информации, кото-
рую содержит документ:
text/html — HTML-документ;
text/plain — простой текстовый файл;
image/gif — GIF-изображение;
image/jpeg — JPG-изображение;
еще несколько десятков других типов.
Pragma
Формат:
Pragma: no-cache
Запрещает кэширование документа браузером, так что при повторном визите на стра-
ницу браузер гарантированно загрузит ее снова, а не извлечет из своего кэша. Это мо-
жет быть полезно, если страница содержит, например, динамический счетчик посеще-
ний.
Заголовок Pragma используется так же и для других целей (и соответственно, после
двоеточия находятся иные значения строки), но мы не будем их здесь рассматривать.
Location
Формат:
Location: http://www.otherhost.com/somepage.html
76 Часть I. Основы Web-программирования
Set-cookie
Формат:
Set-cookie: параметры_cookie
Устанавливает cookie в браузер пользователя. В конце этой главы (см. разд. "Что та-
кое cookies и „с чем их едят“?") мы рассмотрим подробнее, что такое cookies и как
с ними работать.
Date
Формат:
Date: Sat, 08 Jan 2000 11:56:26 GMT
Server
Формат:
Server: nginx/1.4.6 (Ubuntu)
Примеры CGI-сценариев на C
Настало время привести небольшой сценарий на C, иллюстрирующий некоторые воз-
можности, которые были описаны выше (листинг 3.1).
Затем копируем один в один нужный нам GIF-файл в стандартный поток вывода (луч-
ше всего — функцией fwrite, т. к. иначе могут возникнуть проблемы с "бинарностью"
GIF-рисунка). Теперь можно использовать этот сценарий даже в таком контексте:
... какой-то текст страницы ...
<img src="http://www.myhost.com/cgi-bin/script.cgi">
... продолжение страницы ...
В результате таких действий в нашу страницу будет подставляться каждый раз случай-
ное изображение, генерируемое сценарием. Разумеется, чтобы избежать неприятностей
с кэшированием, которое особенно интенсивно применяется браузерами по отношению
к картинкам, мы должны его запретить выводом соответствующего заголовка. Именно
так устроены графические счетчики, столь распространенные в Интернете.
Еще раз обращаем ваше внимание на такой момент: CGI-сценарии могут использовать-
ся не только для вывода HTML-информации, но и для любого другого ее типа — начи-
ная с графики и заканчивая звуковыми MIDI-файлами. Тип документа задается в един-
ственном месте — заголовке Content-type. Не забывайте добавлять этот заголовок,
в противном случае пользователю будет отображена стандартная страница сервера
с сообщением о 500-й ошибке, из которой он вряд ли что поймет.
З АМЕЧАНИЕ
Исходный код скрипта, отображающего GIF-файл, мы здесь не приводим из-за обилия
в нем излишних технических деталей. Вы можете найти его в архиве с исходными кодами
из данной книги, доступном в Интернете. Файл называется c/gif.c.
Переменные окружения
Непосредственно перед запуском сценария сервер передает ему некие переменные ок-
ружения с информацией. В определенных переменных содержатся некоторые заголов-
ки, но, как уже говорилось, не все (получить все заголовки нельзя). Далее приведен пе-
речень наиболее важных переменных окружения (большинство из них мы уже рассмат-
ривали, но сейчас будет полезно повториться и систематизировать весь наш список
именно с точки зрения программиста).
HTTP_ACCEPT
В этой переменной перечислены все (во всяком случае, так говорится в документа-
ции) MIME-типы данных, которые могут быть восприняты браузером. Как мы уже
замечали, современные браузеры частенько ленятся и передают строку */*, озна-
чающую, что они якобы понимают любой тип.
HTTP_REFERER
Задает имя документа, в котором находится форма, запустившая CGI-сценарий. Эту
переменную окружения можно задействовать, например, для того, чтобы отслежи-
Глава 3. CGI изнутри 79
CONTENT_LENGTH
Количество байтов данных, присланных пользователем. Эту переменную необходи-
мо анализировать, если вы занимаетесь приемом и обработкой POST-формы.
int main(void) {
// Получаем значение переменной окружения REMOTE_ADDR
char *remote_addr = getenv("REMOTE_ADDR");
// ... и еще QUERY_STRING
char *query_string = getenv("QUERY_STRING");
// Печатаем заголовок
printf("Content-type: text/html\n\n");
// Печатаем документ
printf("<!DOCTYPE html>");
printf("<html lang='ru'>");
printf("<head>");
printf("<title>Работа с переменными окружения</title>");
printf("<meta charset='utf-8'>");
printf("</head>");
printf("<body>");
printf("<h1>Здравствуйте. Мы знаем о Вас все!</h1>");
printf("<p>Ваш IP-адрес: %s</p>", remote_addr);
printf("</p>Вот параметры, которые Вы указали: %s</p>", query_string);
printf("</body></html>");
}
В НИМАНИЕ !
Следует заметить очень важную деталь: использование метода POST вовсе не означает,
что не был применен также и метод GET. Иными словами, метод POST подразумевает также
возможность передачи данных через URL-строку. Эти данные будут, как обычно, помещены
в переменную окружения QUERY_STRING.
Но как же узнать, сколько именно данных переслал пользователь методом POST? До ка-
ких пор нам читать входной поток? Для этого служит переменная окружения
CONTENT_LENGTH, в которой хранится строка с десятичным представлением числа пере-
данных байтов данных (разумеется, перед использованием ее надо перевести в обычное
число).
Модифицируем предыдущий пример так, чтобы он принимал POST-данные, а также вы-
водил и GET-информацию, если она задана (листинг 3.3).
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// Извлекаем значения переменных окружения
char *remote_addr = getenv("REMOTE_ADDR");
char *content_length = getenv("CONTENT_LENGTH");
char *query_string = getenv("QUERY_STRING");
// Вычисляем длину данных - переводим строку в число
int num_bytes = atoi(content_length);
// Выделяем в свободной памяти буфер нужного размера
char *data = (char *)malloc(num_bytes + 1);
// Читаем данные из стандартного потока ввода
fread(data, 1, num_bytes, stdin);
// Добавляем нулевой код в конец строки
// (в C нулевой код сигнализирует о конце строки)
data[num_bytes] = 0;
// Выводим заголовок
printf("Content-type: text/html\n\n");
// Выводим документ
printf("<!DOCTYPE html>");
printf("<html lang='ru'>");
printf("<head>");
printf("<title>Получение данных POST</title>");
82 Часть I. Основы Web-программирования
printf("<meta charset='utf-8'>");
printf("</head>");
printf("<body>");
printf("<h1>Здравствуйте. Мы знаем о Вас все!</h1>");
printf("<p>Ваш IP-адрес: %s</p>", remote_addr);
printf("<p>Количество байтов данных: %d</p>", num_bytes);
printf("<p>Вот параметры, которые Вы указали: %s</p>", data);
printf("<p>А вот то, что мы получили через URL: %s</p>", query_string);
printf("</body></html>");
}
<!DOCTYPE html>
<html lang='ru'>
<head>
<title>Получение данных POST</title>
<meta charset='utf-8'>
</head>
<body>
<form action="/cgi-bin/script.cgi?param=value" method="POST">
<p>Name1: <input type="text" name="name1"></p>
<p>Name2: <input type="text" name="name2"></p>
<p><input type="submit" value="Запустить сценарий!"></p>
</form>
</body>
</html>
Теперь если мы наберем в полях ввода какой-нибудь текст и нажмем кнопку, то полу-
чим HTML-страницу, сгенерированную сценарием, например, следующего содержа-
ния:
Здравствуйте. Мы знаем о Вас все!
Ваш IP-адрес: 136.234.54.2
Количество байтов данных: 23
Вот параметры, которые Вы указали: name1=Vasya&name2=Petya
А вот то, что мы получили через URL: param=value
Как можно заметить, обработка метода POST устроена сложнее, чем GET. Тем не менее
метод POST используется чаще, особенно если нужно передавать большие объемы дан-
ных или закачивать файл на сервер (эта возможность также поддерживается протоко-
лами HTTP и HTML).
Глава 3. CGI изнутри 83
З АМЕЧАНИЕ
Мы не можем сначала все данные (например, полученные из стандартного потока ввода)
декодировать, а уж потом работать с ними (в частности, разбивать по месту вхождения
символов & и =). Действительно, вдруг после перекодировки появятся символы & и =, кото-
рые могут быть введены пользователем? Как мы тогда узнаем, разделяют ли они парамет-
ры или просто набраны с клавиатуры? Очевидно, никак. Поэтому такой способ нам не под-
ходит, и придется работать с каждым значением отдельно, уже после разделения строки на
части.
// Функция URL-декодирования.
// Функция преобразует строку данных st в нормальное представление.
// Результат помещается в ту же строку, что была передана в параметрах.
void url_decode(char *st) {
char *p = st; // указывает на текущий символ строки
char hex[3]; // временный буфер для хранения %XX
int code; // преобразованный код
// Запускаем цикл, пока не кончится строка (т. е. пока
// не появится символ с кодом 0, см. ниже)
do {
// Если это %-код ...
if(*st == '%') { // тогда копируем его во временный буфер
hex[0] = *(++st);
84 Часть I. Основы Web-программирования
hex[1] = *(++st);
hex[2] = 0;
// Переводим его в число
sscanf(hex, "%X", &code);
// и записываем обратно в строку
*p++ = (char)code;
// Указатель p всегда отмечает то место в строке, в которое
// будет помещен очередной декодированный символ
}
// Иначе, если это "+", то заменяем его на " "
else if(*st == '+') *p++ = ' ';
// А если ни то, ни другое - оставляем, как есть
else *p++ = *st;
} while(*st++ != 0); // пока не найдем нулевой код
}
Функция основана на том свойстве, что длина декодированных данных всегда меньше,
чем кодированных, а значит, всегда можно поместить результат в ту же строку, не опа-
саясь ее переполнения. Конечно, примененный алгоритм далеко не оптимален, т. к. ис-
пользует довольно медлительную функцию sscanf() для перекодирования каждого
символа. Тем не менее это самое простое, что можно придумать, вот почему мы так и
сделали.
Теперь мы можем слегка модифицировать предыдущий пример сценария, заставив его
перед выводом данных декодировать их (листинг 3.6).
int main(void) {
// Получаем значения переменных окружения
char *remote_addr = getenv("REMOTE_ADDR");
char *content_length = getenv("CONTENT_LENGTH");
//!! Выделяем память для буфера QUERY_STRING
char *query_string = malloc(strlen(getenv("QUERY_STRING")) + 1);
//!! Копируем QUERY_STRING в созданный буфер
strcpy(query_string, getenv("QUERY_STRING"));
// Декодируем QUERY_STRING
url_decode(query_string);
// Вычисляем количество байтов данных - переводим строку в число
int num_bytes = atoi(content_length);
// Выделяем в свободной памяти буфер нужного размера
char *data = (char*)malloc(num_bytes + 1);
// Читаем данные из стандартного потока ввода
fread(data, 1, num_bytes, stdin);