/
Автор: Веллинг Л. Томсон Л.
Теги: компьютерные технологии программирование кодирование языки программирования html
ISBN: 978-5-8459-0862-9
Год: 2008
Текст
Рассматриваются версии PHP 5 и MySQL 5!
Разработка
Web-приложений
с помощью РНР
и MySQL
Люк Веллинг
Лора Томсон
------.
ТРЕТЬЕ ИЗДАНИЕ
Разработка Web-приложений
с помощью РНР и MySQL
Третье издание
Люк Веллинг
Лора Томсон
Москва • Санкт-Петербург • Киев
2008
ББК 32.973.26-018.2.75
В27
УДК 681.3.07
Издательский дом “Вильямс”
Зав. редакцией С.Н. Тригуб
Под редакцией/О.//. Артеменко
По общим вопросам обращайтесь в Издательский дом “Вильямс”
по адресу: info@williamspublishing.coin, http://www.williainspublishing.coin
115419, Москва, а/я 783; 03150, Киев, а/я 152
Веллинг, Люк, Томсон, Лора.
В27 Разработка Web-приложений с помощью РНР и MySQL, 3-е издание. : Пер. с
англ. — М.: Издательский дом “Вильямс”, 2008.— 880 с.: ил. — Парад, тит. англ.
ISBN 978-5-8459-0862-9 (рус.)
Эта книга предназначена для тех, кто уже знаком, по крайней мере, с основами HTML
и ранее разрабатывал программы на современных языках программирования, но, воз-
можно, не занимался программированием для Web или не использовал реляционные базы
данных. В ней подробно описано применение РНР и MySQL для построения крупных
коммерческих Web-сайтов. Основное внимание в книге уделяется реальным приложени-
ям. Здесь рассматриваются как простые интерактивные системы приема заказов, так и
различные аспекты электронных систем продажи и безопасности во взаимосвязи с созда-
нием реального Web-сайта. Подробно описаны все стадии разработки множества типовых
проектов на РНР и MySQL, в числе которых, помимо прочих, система управления содер-
жимым, почтовый Web-сайт, приложение поддержки Web-форумов и электронный книж-
ный магазин. Заслуживают особого внимания главы, посвященные объектно-
ориентированному программированию на РНР, динамической генерации PDF-документов
и доступу к стандартным Web-службам, предлагаемым различными компаниями.
Основное отличие этого издания от предыдущего состоит в том, что материалы и
весь исходный код полностью переписаны для новых версий РНР5 и MySQL 5.0.
Книга ориентирована на профессиональных разработчиков, но будет полезной и
для начинающих программистов.
ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соот-
ветствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то
ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая
фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения изда-
тельства Sams Publishing.
Authorized translation from the English language edition published by Sams Publishing Copyright © 2005
All rights reserved. No part of this book may be reproduced or transmitted in anv form or b\ anv means,
electronic or mechanical, including photocopying, recording or by anv information storage retrieval system,
without permission from the Publisher.
Russian language edition is published by Williams Publishing House according to the .Agreement with
R&I Enterprises International, Copyright © 2008
ISBN 978-5-8459-0862-9 (pyc.)
ISBN 0-672-32672-8 (англ.)
© Издательский дом “Вильямс”. 2008
© Sams Publishing, 2005
Оглавление
Часть I. Использование РНР 35
Глава 1. Введение в РНР 36
Глава 2. Сохранение и восстановление данных 80
Глава 3. Использование массивов 103
Глава 4. Манипулирование строками и регулярные выражения 129
Глава 5. Многократное использование кода и создание функций 154
Глава 6. Объектно-ориентированное программирование на РНР 181
Глава 7. Обработка исключений 214
Часть II. Использование MySQL 225
Глава 8. Проектирование баз данных для Web 226
Глава 9. Создание базы данных для Web 238
Глава 10. Работа с базой данных MySQL 261
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР 283
Глава 12. Дополнительные сведения по администрированию MySQL 301
Глава 13. Дополнительные сведения по программированию в MySQL 323
Часть III. Электронная коммерция и безопасность 337
Глава 14. Эксплуатация сайта электронной коммерции 338
Глава 15. Безопасность сайта электронной коммерции 352
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL 373
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL 394
Часть IV. Усовершенствованные технологии РНР 413
Глава 18. Взаимодействие с файловой системой и сервером 414
Глава 19. Использование функций работы с сетью и протоколами 431
Глава 20. Работа с датой и временем 449
Глава 2L Генерация изображений 459
Глава 22. Управление сеансами в РНР 484
Глава 23. Другие полезные свойства 499
Часть V. Реальные проекты на РНР и MySQL 509
Глава 24. Использование РНР и MySQL в крупных проектах 510
Глава 25. Отладка 526
Глава 26. Реализация задачи аутентификации и персонализации посетителей 543
Глава 27. Разработка покупательской тележки 577
Глава 28. Разработка системы управления содержимым 619
Глава 29. Разработка почтовой Web-службы 648
Глава 30. Разработка диспетчера списков рассылки 682
Глава 31. Разработка приложения поддержки Web-форумов 733
Глава 32- Генерация персонифицированных документов в PDF-формате 761
Глава 33. Подключение к Web-службам с помощью XML и SOAP 795
Часть VI. Приложения 837
Приложение А. Инсталляция РНР и MySQL 838
Приложение Б. Ресурсы в Web 861
Предметный указатель 865
Содержание
Об авторах 24
Благодарности 25
От издательства 25
Введение 26
Часть I. Использование РНР 35
Глава 1. Введение в РНР 36
Использование РНР 37
Пример приложения: “Автозапчасти от Боба” 37
Форма заказа 37
Обработка формы 39
Встраивание РНР в HTML 39
Использование РНР-дескрипторов 41
Стили РНР-дескрипторов 41
Операторы РНР 42
Пробелы 42
Комментарии 43
Добавление динамического содержимого 44
Вызов функций 44
Использование функции date() 45
Доступ к переменным формы 45
Переменные формы 45
Конкатенация строк 49
Переменные и литералы 49
Идентификаторы 50
Переменные, объявленные пользователем 50
Присвоение значений переменным 51
Типы переменных 51
Т ипы данных РНР 51
Преимущества использования типов 52
Приведение типов 52
П еременные переменных 5 3
Объявление и использование констант 53
Область действия переменных 54
Использование операций 55
Арифметические операции 55
Строковые операции 56
Операции присваивания 56
Операции сравнения 59
Логические операции 60
П оразрядные операцит i 61
Другие операции 61
Использование операций: вычисление итоговых сумм для формы 63
Приоритет и ассоциативность: вычисление выражений 65
Использование функций для работы с переменными 66
Проверка и установка типов переменных 66
Проверка состояния переменных 67
Повторная интерпретация переменных 68
Реализация управляющих структур 68
Принятие решений на основе условий 69
Операторы if 69
Блоки кода 69
Операторы else 70
Операторы elseif 71
Операторы switch 71
Сравнение различных условных операторов 73
Повторение действий с помощью итераций 74
Циклы while 75
Циклы for и foreach 76
Циклы do. .while 77
Выход из управляющей структуры или сценария 78
Использование альтернативного синтаксиса управляющих структур 78
Использование declare 79
Что дальше 79
Глава 2. Сохранение и восстановление данных 80
Сохранение данных для дальнейшего их использования 80
Сохранение и извлечение заказов в компании “Автозапчасти от Боба” 81
Обработка файлов 82
Открытие файла 82
Режимы файлов 82
Использование функции fopen() для открытия файла 83
Открытие файлов через FTP или HTTP . 85
Проблемы, возникающие при открытии файлов 86
Запись в файл 88
Параметры функции fwrite() 89
Форматы файлов 89
Закрытие файла 90
Считывание из файла 92
Открытие файла для чтения: функция fopen() 93
Как узнать, где остановиться: функция feof() 93
Построчное чтение: функции fgets(), fgetss() и fgetcsv() 94
Чтение всего файла: функции readfile(), fpassthru() и file() 95
Чтение символа: функция fgetc() 96
Чтение строк произвольной длины: функция 1 read() 96
Другие полезные файловые функции 97
Проверка, существует ли файл: функция file_exists() 97
Выяснение размера файла: функция filesize() 97
Удаление файла: функция unlink() 97
Перемещение внутри файла: функции rewind(), fseek() и ftell() 97
Содержание
7
Блокирование файлов 99
Более рациональный способ обработки: системы управления базами данных 100
Проблемы, связанные с использованием двумерных файлов 100
Как эти проблемы решаются с помощью систем управления
реляционными базами данных 101
Дополнительные источники информации 102
Что дальше 102
Глава 3. Использование массивов 103
Что такое массив 104
Численно-индексированные массивы 104
Инициализация численно-индексированных массивов 105
Доступ к содержимому массива 105
Использование циклов для доступа к массиву 106
Массивы с различными индексами 107
Инициализация массива 107
Доступ к элементам массива 107
Использование циклов 107
Операции для работы с массивами 109
М ногомерные массивы 11С
Сортировка массивов 114
И спользован! ie функции sort () 114
Использование функций asort() и ksort() для сортировки массивов 114
Сортировка в обратном порядке 115
Сорти ровка многомерных массивов 115
Определяемые пользователем функции сортировки 115
Определяемые пользователем функции сортировки в обратном порядке 117
Изменение порядка следования элементов в массивах 117
Использование функции shuffle() 118
Использование функции array_reverse() 119
Загрузка массивов из файлов 120
Другие манипуляции с массивами 123
Перемещение внутри массива: функции each(), current(), reset(),
end(), next(), pos() и prev() 123
Применение любой функции к каждому элементу массива:
функция array_walk() 124
Подсчет элементов в массиве: функции count (), sizeof()
и array_count_values() 125
Преобразование массивов в скалярные переменные: функция extract!) 126
Дополнительные источники информации 128
Что дальше 128
Глава 4. Манипулирование строками и регулярные выражения 129
Пример приложения: интеллектуальная форма отправки электронной почты 129
Форматирование строк 132
Усечение строки: функции chop(), ltrim() и trim() 132
Форматирование строк для целей представления 132
8
Содержание
Форматирован!ie строк для хранения: функции addslashes() и stripslashes() 136
Объединение и разделение строк с помощью строковых функций 137
Использование функций explode(), implode() Hjoin() 138
Использование функции strtok() 139
Использование функции substr() 140
Сравнение строк 140
Упорядочение строк: функции strcmp(), strcasecmp() и stmatcmp() 141
Проверка длины строки с помощью функции strlen() 141
Сопоставление и замена подстрок с помощью строковых функций 142
Поиск строк в строках: функции strstr(), strchr(), stn chr() и stristr() 142
Определение позиции подстроки: функции strpos() и strrpos() 143
Замена подстрок: функции str_replace() и substr_ieplace() 144
Введение в регулярные выражения 145
Основы 146
Наборы символов и классы 146
Повторение 147
Подвыражения 148
Подвыражения с подсчетом 148
Привязка к началу или концу строки 148
Ветвление 149
Сопоставление с литеральными значениями специальных символов 149
Краткое описание специальных символов 149
Использование регулярных выражений в приложении
интеллектуальной формы отправки электронной почты 150
Поиск подстрок с помощью регулярных выражений 151
Замена подстрок с помощью регулярных выражений 152
Разделение строк с помощью регулярных выражений 152
Сравнение функций обработки строк и регулярных выражений 153
Дополнительные источники информации 153
Что дальше 153
Глава 5. Многократное использование кода и создание функций 154
Многократное использование кода 154
Стоимость 155
Надежность 155
Единообразие 155
Использование операторов require() и include() 156
require() 156
Расширения имен файлов и оператор require() 157
РHP-дескрипторы и оператор require() 157
Использование оператора require() для шаблонов Web-сайта 158
Использование оператора include!) 162
Использование операторов require_once() и include_once() 163
Использование конфигурационных параметров auto_prepend_file
и auto append file 163
Использование функций в РНР 164
Вызов функций 164
Содержание
9
Вызов неопределенной функции 165
Регистр символов и имена функций 166
Для чего нужно определять собственные функции? 167
Базовая структура функции 167
Именование функций 168
Параметры 169
Область действия 171
Передача по ссылке и передача по значению 174
Возврат из функции 175
Возврат значений из функции 176
Блоки кода 177
Реализация рекурсии 178
Дополнительные источники информации 180
Что дальше 180
Глава 6. Объектно-ориентированное программирование на РНР 181
Концепции объектно-ориентированного программирования 182
Классы и объекты 182
Полиморфизм 183
Наследование 184
Создание классов, атрибутов и операций в РНР 185
Структура класса 185
Конструкторы 185
Деструкторы 186
Создание экземпляров класса 186
Использование атрибутов класса 187
Управление доступом с помощью модификаторов private и public 189
Вызов операций класса 190
Реализация наследования в РНР 190
Управление видимостью при наследовании с помощью
модификаторов private и protected 191
Перекрытие 193
Предотвращение наследования и перекрытия с помощью final 194
Множественное наследование 195
Реализация интерфейсов 196
Проектирование классов 196
Написание кода класса 197
Дополнительная объектно-ориентированная функциональность РНР 205
Сравнение РНР4 и РНР5 205
Использование констант класса 206
Реализация статических методов 206
Проверка типа объекта и указание типов 206
Клонирование объектов 207
Использование абстрактных классов 208
Перегрузка методов с помощью_са11() 208
Использование___autoload() 209
Реализация итераторов и итерации 209
10
Содержание
Преобразование классов в строки 211
Использование Reflection API 212
Что дальше 212
Глава 7. Обработка исключений 214
Концепции обработки ошибок 214
Класс Exception 216
Исключения, определяемые пользователем 217
Исключения в приложении “Автозапчасти от Боба” 219
Исключения и другие механизмы обработки ошибок РНР 222
Дополнительные источники информации 223
Что дальше 223
Часть II. Использование MySQL 225
Г лава 8. Проектирование баз данных для Web 226
Концепции реляционных баз данных 227
Таблицы 227
Столбцы 228
Строки 228
Значения 228
Ключи 228
Схемы 229
Отношения 230
Как спроектировать собственную базу данных для Web 230
Думайте о реальных объектах, которые вы моделируете 230
Избегайте хранения избыточной информации 231
Используйте элементарные значения столбцов 233
Выбирайте подходящие ключи 234
Подумайте над вопросами, которые потребуется задать базе данных 234
Избегайте проектов с большим количеством пустых атрибутов 234
Типы таблиц 235
Архитектура баз данных для Web 235
Архитектура 235
Дополнительная информация 237
Что дальше 237
Глава 9. Создание базы данных для Web 238
Использование монитора MySQL 239
Вход в MySQL 240
Создание баз данных и пользователей 241
Создание базы данных 241
Настройка пользователей и полномочий 242
Знакомст во с системой полномочий MySQL 242
Принцип минимально необходимых полномочий 242
Настройка пользователей: команда GRANT 243
Типы и уровни полномочий 244
Команда REVOKE 246
Содержание
11
Примеры использования команд GRANT и REVOKE 247
Установка пользователя для доступа из Web 247
Выход из системы привилегированного пользователя 248
Использование требуемой базы данных 248
Создание таблиц баз данных 249
Значения других ключевых слов 250
Что означают типы столбцов 251
Просмотр базы данных с помощью команд SHOW и DESCRIBE 253
Создание индексов 254
Замечание по поводу типов таблиц 254
Идентификаторы MySQL 254
Типы данных столбцов 255
Числовые типы 256
Дополнительные источники информации 260
Что дальше 260
Глава 10. Работа с базой данных MySQL 261
Что такое SQL? 261
Вставка данных в базу данных 262
Извлечение данных из базы данных 264
Извлечение данных по определенному критерию 265
Извлечение данных из нескольких таблиц 267
Извлечение данных в определенном порядке 272
Группировка и агрегирование данных 2 73
Выбор возвращаемых строк 275
Использование подзапросов 275
Обновление записей в базе данных 278
Изменение таблиц после создания 278
Удаление записей из базы данных 280
Удаление таблиц 281
Удаление целой базы данных 281
Дополнительные источники информации 281
Что дальше 282
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР 283
Как работает архитектура баз данных для Web 284
Выполнение запросов к базе данных из Web 287
Проверка и фильтрация входных данных 287
Установка соединения 288
Выбор базы данных 289
Выполнение запроса к базе данных 290
Получение результатов запроса 290
Отсоединение от базы данных 291
Внесение новой информации в базу данных 292
Использование подготовленных операторов 295
Использование других PHP-интерфейсов работы с базами данных 297
Использование обобщенного интерфейса базы данных: PEAR DB 297
12
Содержание
Дополнительные источники информации 300
Что дальше 300
Глава 12. Дополнительные сведения по администрированию MySQL 301
Подробное ознакомление с системой полномочий 301
Таблица user 302
Таблицы db и host 304
Таблицы tables_priv и columns__priv 305
Управление доступом: использование таблиц полномочий в среде MySQL 306
Обновление полномочий: когда изменения вступают в силу? 306
Обеспечение безопасности базы данных MySQL 307
MySQL с точки зрения операционной системы 307
Пароли 307
Полномочия пользователя 308
Проблемы, связанные с Web 309
Получение дополнительной информации о базах данных 309
Получение информации с помощью оператора SHOW 309
Получение информации о столбцах с помощью оператора DESCRIBE 310
Получение информации о способе выполнения запросов
с помощью оператора EXPLAIN 312
Ускорение выполнения запросов за счет использования индексов 317
Оптимизация базы данных 317
Оптимизация проекта 317
Разрешения 317
Оптимизация таблиц 317
Использование индексов 318
Использование значений, заданных по умолчанию 318
Дополнительные советы 318
Резервное копирование базы данных MySQL 318
Восстановление базы данных MySQL 319
Реализация репликации 319
Настройка ведущего сервера 320
Выполнение первоначальной передачи данных 321
Настройка одного или нескольких ведомых серверов 321
Дополнительные источники информации 322
Что дальше 322
Глава 13. Дополнительные сведения по программированию в MySQL 323
Оператор LOAD DATA INFILE 323
Механизмы хранения 324
Транзакции 325
Определения транзакций 325
Использование транзакций в сочетании с таблицами InnoDB 326
Внешние ключи 327
Хранимые процедуры 328
Простой пример 328
Локальные переменные 331
Содержание
13
Курсоры и управляющие структуры 331
Дополнительные источники информации 335
Что дальше 335
Часть III. Электронная коммерция и безопасность 337
Глава 14. Эксплуатация сайта электронной коммерции 338
Определение целей, которые должны быть достигнуты 338
Типы коммерческих Web-сайтов 339
Сетевые брошюры 339
Прием заказов на товары и услуги 342
Предоставление услуг и цифровых товаров 346
Дополнительные товары и услуги 347
Снижение расходов 347
Риски и угрозы 348
Взломщики 348
Невозможность привлечения компаньонов 349
Отказы оборудования 349
Сбои питания, коммуникационных линий, сети и службы доставки 350
Интенсивная конкуренция 350
Ошибки программного обеспечения 350
Изменения в политике и налогообложении 350
Ограниченная пропускная способность системы 351
Выбор стратегии 351
Что дальше 351
Глава 15. Безопасность сайта электронной коммерции 352
Важность вашей информации 353
Угрозы безопасности 353
Вскрытие конфиденциальных данных 354
Потеря или разрушение данных 356
Изменение данных 356
Отказ в обслуживании 357
Ошибки программного обеспечения 358
Отказ от обязательств 360
Простота использования, производительность и безопасность 361
Разработка стратегии безопасности 361
Принципы аутентификации 362
Аутентификация 363
Основы шифрования 364
Шифрование с закрытым ключом 365
Шифрование с открытым ключом 366
Цифровые подписи 366
Цифровые сертификаты 367
Безопасные Web-серверы 368
Аудит и регистрация 369
Брандмауэры 370
Резервное копирование данных 370
14
Содержание
Резервное копирование общих файлов 371
Резервное копирование и восстановление баз данных MySQL 371
Физическая безопасность 371
Что дальше 372
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL 373
Идентификация посетителей 373
Реализация контроля доступа 374
Хранение паролей 377
Шифрование паролей 379
Защита нескольких страниц 381
Базовая аутентификация 382
Использование базовой аутентификации в РНР 383
Использование базовой аутентификации с помощью
файлов .htaccess сервера Apache 385
Использование базовой аутентификации в IIS 388
Использование аутентификации с помощью модуля mod_auth_mysql 390
Установка модуля mod_auth_mysql 391
Ну, как, работает? 391
Использование модуля mod_auth_mysql 392
Создание собственного метода аутентификации 393
Дополнительные источники информации 393
Что дальше 393
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL 394
Обеспечение безопасности транзакций 394
Компьютер пользователя 395
Internet 396
Ваша система 397
Использование протокола защищенных сокетов (SSL) 398
Проверка данных, вводимых пользователем 401
Обеспечение безопасного хранения данных 402
Определение необходимости хранения номеров кредитных карточек 404
Использование шифрования в РНР 404
Инсталляция GPG 405
Тестирование GPG 408
Дополнительные источники информации 412
Что дальше 412
Часть IV. Усовершенствованные технологии РНР 413
Глава 18. Взаимодействие с файловой системой и сервером 414
Загрузка файлов 414
HTML-код для загрузки файла 415
Замечания по поводу безопасности 416
Написание PHP-сценария для работы с файлами 416
Часто встречающиеся проблемы 420
Использование функций работы с каталогами 421
Содержание
15
Чтение содержимого каталога 421
II олучение информации о текущем каталоге 423
Создание и удаление каталогов 423
Взаимодействие с файловой системой 424
Получение информации о файле 424
Изменение свойств файла 426
Создание, удаление и перемещение файлов 427
Использование функций запуска программ 427
Взаимодействие с окружением: функции getenv() и putenv() 430
Дополнительные источники информации 430
Что дальше 430
Глава 19. Использование функции работы с сетью и протоколами 431
Обзор сетевых протоколов 431
Отправка и получение почты 432
Использование других Web-сайтов 432
Применение функций сетевого контроля 435
Использование FTP 439
Использование FTP для резервного и зеркального копирования файла 440
Загрузка файлов на сервер 446
Как избежать тайм-аутов 446
Другие функции работы с FTP 447
Дополнительные источники информации 447
Что дальше 448
Глава 20. Работа с датой и временем 449
Получение даты и времени средствами РНР 449
Использование функции date() 449
Работа с метками времени Unix 451
Использование функции getdate() 452
Проверка правильности дат 453
Преобразования дат между форматами РНР и MySQL 453
Операции над датами 455
Операции над датами в MySQL 456
Использование микросекунд 457
Использование календарных функций 457
Дополнительные источники информации 458
Что дальше 458
Г лава 21. Г енерация изображений 459
Настройка поддержки изображений в РНР 460
Форматы изображений 460
JPEG 460
PNG 461
WBMP 461
GIF 461
Создание изображений 462
16
Содержание
Создание холста 463
Рисование и вывод текста в изображение 464
Вывод финального изображения 466
Освобождение ресурсов 467
Использование автоматически сгенерированных изображений
на других страницах 467
Использование текста и шрифтов при создании изображений 468
Настройка базового холста 471
Подбор размера текста на кнопке 472
Позиционирование текста 474
Вывод текста на кнопку 475
Заключительные действия 475
Вычерчивание фигур и построение графиков 475
Другие функции обработки изображений 482
Дополнительные источники информации 483
Что дальше 483
Глава 22. Управление сеансами в РНР 484
Что такое управление сеансами 484
Базовая функциональность сеансов 484
Что такое cookie-набор? 485
Установка cookie-наборов из РНР 485
Использование cookie-наборов в сеансах 486
Сохранение идентификатора сеанса 486
Реализация простых сеансов 487
Запуск сеанса 487
Регистрация переменных сеанса 487
Использование переменных сеанса 488
Разрегистрация переменных и уничтожение сеанса 488
Пример простого сеанса 489
Конфигурирование управления сеансами 491
Реализация аутентификации средствами управления сеансами 491
Дополнительные источники информации 498
Что дальше 498
Глава 23. Другие полезные свойства 499
Использование магических кавычек 499
Выполнение команд, содержащихся в строке, с помощью функции eval() 500
Прекращение выполнения с помощью die и exit 501
Сериализация переменных и объектов 502
Получение информации об окружении РНР 503
Определение загруженных расширений 503
Определение владельца сценария 504
Определение даты последнего изменения сценария 504
Динамическая загрузка расширений 504
Временное изменение среды исполнения 504
Выделение цветом элементов исходного кода 505
Содержание
17
Использование РНР в командной строке 506
Что дальше 507
Часть V. Реальные проекты на РНР и MySQL 509
Глава 24. Использование РНР и MySQL в крупных проектах 510
Применение методов проектирования программного обеспечения
при разработке Web-приложений 511
Планирование и сопровождение проекта Web-приложения 511
Многократное использование кода 512
Написание удобного в сопровождении кода 513
Стандарты написания кода 513
Управление версиями 518
Выбор среды разработки 519
Документирование проектов 520
Создание прототипов 521
Разделение логики и содержимого 521
Оптимизация кода 522
Использование простой оптимизации 522
Использование продуктов Zend 523
Тестирование 524
Дополнительные источники информации 525
Что дальше 525
Глава 25. Отладка 526
Программные ошибки 526
Синтаксические ошибки 526
Ошибки времени выполнения 528
Логические ошибки 534
Вспомогательное средство отладки переменных 535
Уровни выдачи сообщений об ошибках 537
Изменение настроек уровня сообщения об ошибках 538
Генерация собственных ошибок 540
Изящная обработка ошибок 540
Что дальше 542
Глава 26. Реализация задачи аутентификации
и персонализации посетителей 543
Задача 543
Komi i оненты решения 544
Идентификация и персонализация пользователей 544
Хранение закладок 545
Рекомендация закладок 545
Обзор решения 545
Реализация базы данных 547
Реализация базового варианта сайта 548
Реализация аутентификации пользователей 550
Регистрация 551
18
Содержание
Вход в систему 557
Выход из системы 560
Смена паролей 561
Переустановка забытых паролей 563
Реализация хранения и извлечения закладок 567
Добавление закладок 567
Отображение закладок 570
Удаление закладок 571
Выработка рекомендаций 572
Заключение и возможные расширения 576
Что дальше 576
Глава 27. Разработка покупательской тележки 577
Задача 577
Компоненты решения 578
Построение онлайнового каталога 578
Отслеживание выбираемого товара 578
Реализация платежной системы 579
Разработка интерфейса администрирования 579
Обзор решения 580
Создание базы данных 583
Реализация онлайнового каталога 585
Вывод списка категорий 586
Вывод списка книг, относящихся к заданной категории 590
Вывод информации о конкретной книге 591
Реализация покупательской тележки 592
Использование сценария show_cart.php 593
Вывод содержимого тележки 596
Добавление элементов в тележку 598
Сохранение изменений содержимого тележки 600
Печать итоговых данных в строке заголовка 601
Выполнение окончательного расчета 601
Реализация платежа 607
Реализация интерфейса администрирования 609
Расширение проекта 617
Использование существующей системы 617
Что дальше 618
Глава 28. Разработка системы управления содержимым 619
Задача 619
Требования к проекту 620
Существующие системы 620
Редактирование содержимого 620
Ввод содержимого в систему 620
Преимущество хранения содержимого в базах данных перед файлами 621
Структура документов 622
Использование метаданных 622
Содержание 19
Форматирование вывода 623
Обзор решения 624
Проектирование базы данных 625
Реализация системы CMS 627
Интерфейсная часть 627
Манипуляции изображениями 630
Прикладная часть 633
Поиск статей 642
Окно редактора 645
Расширение проекта 646
Что дальше 647
Глава 29. Разработка почтовой Web-службы 648
Задача 648
Компоненты решения 649
Обзор решения 651
Создание базы данных 652
Архитектура сценария 654
Вход и выход из системы 659
Наст ройка учетных записей 662
Создание новой учетной записи 664
Изменение существующей учетной записи 666
Удаление учетной записи 666
Чтение почтовых сообщений 667
Выбор учетной записи 667
Просмотр содержимого почтового ящика 670
Чтение почтовых сообщений 673
Просмотр заголовков сообщений 675
Удаление почтовых сообщений 676
Отправка почты 677
Отправка нового сообщения 677
Ответ или переадресация сообщения 679
Расширение проекта 681
Что дальше 681
Глава 30. Разработка диспетчера списков рассылки 682
Задача 683
Компоненты решения 683
Создание базы данных списков и подписчиков 683
Загрузка файлов 684
Отправка сообщений электронной почты с вложениями 685
Обзор решения 685
Создание базы данных 687
Архитектура сценария 690
Реализация процедуры входа в систему 697
Создание новой учетной записи 698
Вход в систему 700
20
Содержание
Реализация функций пользователя 702
Просмотр списков рассылки 703
Просмотр сведений о списке рассылки 708
П росмотр архивов списков рассылки 710
Подписка и отмена подписки 711
Изменение параметров настройки учетной записи 712
Изменение пароля 712
Выход из системы 714
Реализация функций администратора 715
Создание нового списка рассылки 715
Загрузка нового информационного бюллетеня 717
Обработка загрузки нескольких файлов 720
Предварительный просмотр информационного бюллетеня 725
Отправка сообщения 726
Расширение проекта 731
Что дальше 732
Глава 31. Разработка приложения поддержки Web-форумов 733
Задача 733
Компоненты решения 734
Обзор решения 735
Создание базы данных 737
Просмотр дерева статей 739
Разворачивание и сворачивание 741
Отображение статей 744
Использование класса treenode 745
Просмотр отдельных статей 751
Добавление новых статей 753
Расширение проекта 759
И ci юльзование существующих систем 760
Что дальше 760
Глава 32. Генерапия персонифицированных документов в PDF-формате 761
Задача 761
Оценка форматов документов 762
Бумажная копия 762
ASCII-формат 763
HTML-формат 763
Форматы текстовых процессоров 763
Расширенный текстовый формат 764
PostScript-формат 765
Переносимый формат документов 766
Компоненты решения 767
Система вопросов и ответов 767
Программное обеспечение для генерации документов 767
Программное обеспечение для создания шаблона RTF-документов 767
Программное обеспечение для создания шаблона PDF-документов 768
Содержание 21
Программное обеспечение для создания PDF-документов с помощью кода 769
Обзор решения 770
Задание вопросов 771
Оценка ответов 773
Генерация RTF-сертификата 774
Генерация PDF-сертификата из шаблона 778
Генерация PDF-документа с использованием библиотеки PDFlib 781
Простейший сценарий для PDFlib 782
Генерация сертификата с помощью PDFlib 786
Решение проблем, связанных с заголовками 793
Расширение проекта 793
Дополнительные источники информации 794
Что дальше 794
Глава 33. Подключение к Web-службам с помощью XML и SOAP 795
Задача 795
Основы XML 796
Основы Web-служб 800
Компоненты решения 802
Построение покупательской тележки 802
Использование интерфейсов Web-служб Amazon 802
Разбор XML-документа 803
Использование SOAP с РНР 803
Кэширование 803
Обзор решения 804
Ядро приложения 808
Отображение книг конкретной категории 814
Извлечение класса AmazonResultSet 816
Использование метода REST/ХМ1. поверх HTTP 822
Использование метода SOAP 827
Кэширование данных 828
Реализация покупательской тележки 830
Оплата на сай ге Amazon 833
Инсталляция кода проекта 834
Расширение проекта 835
Дополнительные источники информации 835
Часть VI. Приложения 837
Приложение А. Инсталляция РНР и MySQL 838
Запуск РНР как CGI-интерпрегатора или модуля 839
Инсталляция Apache, РНР и MySQL на Unix-машине 839
Инсталляция бинарных файлов 839
Инсталляция исходных кодов 840
Фрагменты файла httpd.conf 847
Работает ли поддержка РНР? 848
Работает ли SSL? 849
Инсталляция Apache, РНР и MySQL на Windows-машине 850
22
Содержание
Инсталляция MySQL под Windows 851
Инсталляция Apache под Windows 854
Инсталляция РНР под Windows 856
Инсталляция PEAR 859
Настройка других конфигураций 860
Приложение Б. Ресурсы в Web 861
Ресурсы, посвященные РНР 861
Ресурсы, посвященные MySQL и SQL 863
Ресурсы, посвященные Apache 864
Разработка Web-приложений 864
Предметный указатель 865
Содержание
23
Об авторах
Лора Томсон (Laura Thomson) читает лекции в школе компьютерных наук и ин-
формационных технологий при университете RMIT в Мельбурне, Австралия. Одно-
временно она является одним из партнеров известной в области Web-разработки
компании Tangled Web Design. Ранее Лора работала в компании Telstra и Бостонской
консалтинговой группе (Boston Consulting Group). Она имеет степень бакалавра при-
кладных наук (специализация “Компьютерные науки”) и степень с отличием бакалав-
ра технических наук (специализация “Разработка компьютерных систем”). В настоя-
щее время завершает работу над диссертацией на соискание звания доктора
философии по теме адаптивных Web-сайтов. В свободное время любит поспать. С Ло-
рой можно связаться по электронной почте по адресу laura@tangledweb. com. au.
Люк Веллинг (Luke Welling) — ведущий разработчик Web-приложений в компании
MySQL АВ, которая является создателем системы управления базами данных MySQL.
Ранее он читал лекции в школе компьютерных наук и информационных технологий
при университете RMIT в Мельбурне, Австралия, и работал программистом на про-
тяжении множества лет’. Люк имеет степень бакалавра прикладных наук (специализа-
ция “Компьютерные науки”). В свободное время пытается бороться с бессонницей.
С Люком можно связаться по электронной почте по адресу luke@tangledweb. com.au.
Оба автора прошли сертификацию MySQL Core Certification (Основная сертифи-
кация MySQL) компании MySQL АВ и сертификацию Zend Certified РНР Engineer
(Специалист по РНР, сертифицированный компанией Zend) компании Zend Tech-
nologies Ltd.
О соавторах
Исраэль Дэнис-мл. (Israel Denis Jr.) — независимый консультант по системам
электронной коммерции, сотрудничающий со многими компаниями по всему миру.
Он специализируется на интеграции таких пакетов планирования ресурсов предпри-
ятия, как SAP и Lawson, со специализированными решениями для Web. Когда он не
занят разработкой программного обеспечения или написанием очередной книги, он
любит посещать Италию, которую считает своим домом. В 1998 году Исраэль получил
степень магистра по специальности электромашиностроения в техническом коллед-
же Джорджии. Атланта, шт. Джорджия. Он является автором многочисленных ста-
тей, посвященных Linux, Apache, РНР и MySQL. Кроме того, Исраэль сотрудничает с
такими известными компаниями, как General Electric и Procter & Gamble, по вопро-
сам использования компьютерных систем на базе Unix. С ним можно связаться по
электронной почте по адресу idenis@ureach.com.
Крис Ньюман (Chris Newman) — программист-консультант, специализирующийся
на разработке динамических Internet-приложений. Он обладает большим опытом в
области создания широкого спектра приложений с помощью РНР и MySQL для мно-
жества клиентов по всему миру. Он выпускник Кильского университета и живет в
Сток-он-Тренте, Англия, где руководит компанией Lightwood Consultancy Ltd., осно-
ванной им же в 1999 году и специализирующейся на разработке программного обес-
печения для Internet. Крис был поражен потенциальными возможностями Internet
еще во время учебы в университете, поэтому всячески стремился освоить наиболее
современные технологии. Более подробную информацию о компании Lightwood
Consultancy Ltd. можно найти на Web-сайте по адресу http://www.lightwood.net,
а непосредственно с Крисом можно связаться по электронной почте по адресу
chris@lightwood.net.
Благодарности
Мы хотели бы поблагодарить коллектив издательства Sams за проделанную ими
большую работу. В частности, мы хотели бы выразить признательность Шелли
Джонстон (Shelley Jonston), без чьей преданности и терпения издание этой книги
вряд ли оказалось бы возможным. Мы хотели бы также поблагодарить Исраэля Дени-
са-мл. и Криса Ньюмена за их значительный вклад в этот проект.
Мы высоко ценим работу, проделанную командами разработчиков РНР и MySQL.
Все что они делали, существенно облегчало нам жизнь в течение нескольких про-
шедших лет, и это продолжается до сих пор.
Мы благодарим Эдриана Клоуза (Adrian Close) за то, что в 1998 году он сказал: “Вы
можете создать это на РНР”. Еще он сказал, что нам должен понравиться РНР, и, по-
хоже, он оказался прав.
Наконец, мы хотели бы выразить благодарность своим семьям и друзьям, которые
мирились с нашим затворничеством в лучшие времена года. Особенно мы благодар-
ны за поддержку членам наших семей: Джулии. Роберту. Мартину. Лесли, Адаму, По-
лу, Арчеру и Бартону.
От издательства
Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим ва-
ше мнение и хотим знать, что было сделано нами правильно, что можно было сделать
лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и
любые другие замечания, которые вам хотелось бы высказать в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бу-
мажное или электронное письмо, либо просто посетить наш Web-сервер и оставить
свои замечания там. Одним словом, любым удобным для вас способом дайте нам
знать, нравится или нет вам эта книга, а также выскажите свое мнение о том. как сде-
лать наши книги более интересными для вас.
Посылая письмо или сообщение, не забудьте указать название книги и ее авторов,
а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обя-
зательно учтем его при отборе и подготовке к изданию последующих книг.
Наши координаты:
E-mail: inf o@williamspubli shing. com
WWW: http: //www.williamspublishing. com
Информация для писем из:
России: 115419, Москва, а/я 783
Украины: 03150, Киев, а/я 152
Благодарности
25
Введение
У вас в руках третье издание книги Разработка Web-приложений с помощью РНР и
MySQL. На ее страницах вы найдете наиболее важные сведения, которые представ-
ляют собой квинтэссенцию продолжительного опыта использования авторами РНР и
MySQL — двух наиболее популярных инструментальных средств разработки для Web.
Во введении мы коснемся следующих вопросов:
Для чего следует прочесть эту книгу.
Чего можно добиться, используя эту книгу.
Что собой представляют системы РНР и MySQL и чем они хороши.
Обзор новых возможностей РНР 5.0 и MySQL 5.0.
Как построена эта книга.
Что ж, приступим.
Для чего следует прочесть эту книгу
Эта книга призвана научить вас создавать интерактивные Web-сайты, начиная с
простейшей формы заказа и завершая сложными и безопасными сайтами электрон-
ной коммерции. Более того, вы узнаете, как это делать с использованием технологий
программного обеспечения с открытым исходным кодом (Open Source).
Эта книга ориентирована на читателей, которые уже знакомы, как минимум, с
основами языка HTML и ранее разрабатывали приложения на современных языках
программирования, но, возможно, еще не занимались программированием для
Internet и не использовали реляционные базы данных. Без сомнений, книга окажется
полезной для начинающих программистов, однако для более качественного усвоения
изложенного материала им может потребоваться более длительный период. Мы ста-
рались не оставить без внимания ни одну из базовых концепций, однако освещаем их
довольно-таки кратко. В основном, книга адресована тем читателям, которые стре-
мятся овладеть РНР и MySQL для построения крупных и/или коммерческих Web-
сайтов. Эта книга поможет быстрее приступить к делу также и профессиональным
разработчикам, желающим перейти на другой язык написания Web-приложений.
Мы подготовили первое издание данной книги, поскольку изрядно устали от книг,
посвященных РНР, которые, по сути дела, являлись справочниками по функциям.
Конечно, такие книги полезны, однако они не могут помочь в ситуации, когда, ска-
жем, ваш начальник или клиент говорит: “Сделайте-ка мне покупательскую тележку”.
Мы сделали все от нас зависящее, чтобы примеры кода в этой книге были максималь-
но полезными. Многие из примеров кода могут внедряться в разрабатываемый вами
Web-сайт непосредственно, а множество других примеров — лишь с незначительными
модификациями.
Чего можно добиться, используя эту книгу
После внимательного прочтения этой книги вы сможете уверенно разрабатывать
реальные динамические Web-сайты. Если вам доводилось ранее строить Web-сайты с
использованием простого языка HTML, то вы должны быть знакомы со всеми его
ограничениями. При использовании статического содержимого, созданного на осно-
26
Введение
ве чистого HTML-кода, Web-сайт будет таким же статическим. Он останется неиз-
менным, если только не обновить его физически. Пользователи не могут взаимодей-
ствовать с сайтом подобного рода каким-то осмысленным образом.
Применение языка, подобного РНР, и такой базы данных, как MySQL, позволяет
сделать сайты динамическими: они могут настраиваться и содержать информацию,
изменяемую в реальном времени.
В данной книге, даже во вводных главах, основное внимание акцентируется на ре-
альных приложениях. Все начинается с рассмотрения простой интерактивной сис-
темы заказов, а затем предлагается ознакомление с различными составными частями
РНР и MySQL.
Затем мы рассмотрим все аспекты электронной коммерции и безопасности во
взаимосвязи с созданием реального Web-сайта и покажем, как практически реализо-
вать эти аспекты в среде РНР и MySQL.
В заключительной части книги мы обсудим подход к выполнению реальных про-
ектов и ознакомим читателей с разработкой, планированием и реализацией следую-
щих проектов:
Аутентификация и персонализация пользователей.
Электронные покупательские тележки.
Системы управления содержимым.
Электронная почта, основанная на Web.
Диспетчеры списков рассылки.
Web-форумы.
Генерация PDF-документов.
Подключение к Web-службам с помощью XML и SOAP.
Любой из этих проектов может использоваться в предложенном виде или же мо-
дифицироваться в соответствие с конкретными требованиями. Мы сделали такую
подборку проектов потому, что, по нашему мнению, они представляют собой наибо-
лее широко используемые Web-приложения, которые приходится создавать про-
граммистам во всем мире. Если перед вами стоят другие задачи, все равно эта книга
поможет в достижении поставленных целей.
Что такое РНР?
РНР — это серверный (то есть серверной стороны) язык сценариев, разработан-
ный специально для Web. В HTML-страницу можно внедрить PHP-код, который будет
выполняться при каждом ее посещении. PHP-код интерпретируется Web-сервером и
генерирует HTML-код или другой вывод, наблюдаемый посетителями страницы.
Разработка РНР была начата в 1994 году и вначале осуществлялась одним челове-
ком, Расмусом Лердорфом (Rasmus Lerdorf). Впоследствии этот язык адаптировался
многими талантливыми людьми и прошел через четыре основных редакции, пока не
стал широко используемым и зрелым продуктом, с которым мы имеем дело в настоя-
щее время. По состоянию на август 2004 года он использовался в более чем четырна-
дцати миллионах доменов, разбросанных по всем)’ миру, причем их число довольно-
Введение
27
таки быстро увеличивается. Текущее количество доменов, в которых используется
РНР, можно посмотреть по адресу http: / /www.php.net/usage.php.
РНР — это продукт с открытым исходным кодом (Open Source), что означает, что
вы имеете доступ к исходному коду. Его можно использовать, изменять и свободно
распространять другим пользователям или организациям.
Первоначально РНР было сокращением от Personal Ноте Page (Персональная домаш-
няя страница), но затем это название было изменено в соответствии с соглашением
по рекурсивному именованию GNU (GNU = Gnu's Not Unix) и теперь означает РНР
Hypertext Preprocessor (Гипертекстовый препроцессор РНР).
В настоящее время текущей версией РНР является пятая. Эта версия характеризу-
ется полной переделкой механизма Zend, лежащего в основе РНР, и рядом сущест-
венных языковых усовершенствований.
Домашняя страница РНР доступна по адресу http: / /www. php. net.
Домашняя страница Zend Technologies находится по адресу’ http: I /www. zend. com.
Что такое MySQL?
MySQL — очень быстрая и надежная система управления реляционными базами данных
(СУРБД). База данных позволяет эффективно хранить, искать, сортировать и вы-
бирать информацию. Сервер MySQL управляет доступом к данным, позволяя рабо-
тать с ними одновременно нескольким пользователям, обеспечивает быстрый дос-
туп к данным и гарантирует предоставление доступа только тем пользователям,
которые имеют на это право. Следовательно, MySQL является многопользователь-
ским. многопоточным сервером. В нем применяется SQL (Structured Query Language -
язык структурированных запросов), используемый по всему’ миру стандартный язык за-
просов в базы данных. MySQL появился на рынке в 1996 году, однако его разработка
была начата еще в 1979 году. В настоящее время MySQL представляет собой наиболее
популярную СУРБД с открытым исходным кодом; эта система завоевала приз чита-
тельских симпатий в журнале Linux Journal.
Пакет MySQL доступен по двойной схеме лицензирования. Его можно свободно
использовать в соответствие с общедоступной лицензией GNU (GPL) до тех пор, по-
ка соблюдаются требования упомянутой лицензии. Если возникает необходимость в
распространении приложений, не подпадающих под действие лицензии GPL, для
таких ситуаций доступна коммерческая лицензия.
Для чего следует использовать РНР и MySQL?
Для создания сайта электронной коммерции применяется множество различных
продуктов.
Возникает необходимость в выборе следующих компонентов;
Оборудование Web-сервера.
Операционная система.
Программное обеспечение Web-сервера.
Система управления базами данных.
Язык программирования или написания сценариев.
28
Введение
Выбор некоторых из этих компонентов будет зависеть от уже произведенных вы-
боров. Например, не все операционные системы могут работать на любом оборудо-
вании, не все языки написания сценариев могут обеспечивать подключение ко всем
базам данных и так далее.
В этой книге не уделяется особое внимание оборудованию, операционным систе-
мам и программному обеспечению Web-сервера. Нам это не требуется. Одно из заме-
чательных свойств РНР и MySQL состоит в том, что они доступны для широкого
спектра операционных систем, как популярных, так и редких.
Для демонстрации этого утверждения в книге подготовлено множество примеров,
которые были протестированы на двух популярных вариантах установки:
Linux с использованием Web-сервера Apache.
Microsoft Windows ХР с использованием сервера Microsoft Internet Information
Server (IIS).
Какое бы аппаратное обеспечение, операционная система или Web-сервер не бы-
ли бы выбраны, мы уверены, что вы всерьез задумаетесь об использовании РНР и
MySQL.
Некоторые преимущества РНР
В число главных конкурентов РНР входят Perl, Microsoft ASP.NET, JavaServer Pages
(JSP) и ColdFusion.
PHP обладает множеством преимуществ по сравнению с этими продуктами, среди
которых наиболее значительными являются:
Высокая производительность.
Наличие интерфейсов к множеству систем управления базами данных.
Встроенные библиотеки для выполнения многих общих задач, связанных с
Web.
Низкая стоимость.
Простота изучения и использования.
Устойчивая поддержка объектно-ориентированного программирования.
Переносимость.
Доступность исходного кода.
Доступность поддержки со стороны разработчиков.
Ниже эти преимущества рассматриваются более подробно.
Производительность
Система РНР исключительно эффективна. Используя единственный недорогой
сервер, можно обслуживать миллионы обращений в день. В случае применения
большого числа взаимодействующих серверов производительность становится прак-
тически неограниченной. Результаты тестирования, опубликованные компанией
Zend Technologies (http: / /www. zend.com), подтверждают более высокую производи-
тельность РНР по сравнению с конкурирующими продуктами.
Введение
29
Интеграция с базами данных
РНР обладает встроенной возможностью подключения ко многим системам
управления базами данных. В дополнение к MySQL, среди прочих, можно непо-
средственно подключаться к базам данных PostgreSQL, mSQL, Oracle, dbm, FilePro,
Hyperware, Informix, InterBase и Sybase. В PHP 5 также реализован встроенный SQL-
интерфейс для работы с двумерными (плоскими) файлами.
Используя стандарт открытого интерфейса взаимодействия с базами данных (Open
Database Connectivity — ODBC), можно подключаться к любой базе данных, для которой
существует ODBC-драйвер. Это правило распространяется на продукты как Microsoft,
так и множества других компаний.
Встроенные библиотеки
Поскольку' РНР был разработан для использования в Web, он имеет множество
встроенных функций для выполнения большого разнообразия полезных задач, свя-
занных с Web. С его помощью можно на лету генерировать GIF-изображения, под-
ключаться к Web- и другим сетевым службам, выполнять XML-разбор, отправлять со-
общения электронной почты, работать с cookie-наборами и генерировать PDF-
документы — причем все это с помощью всего нескольких строк кода.
Стоимость
Пакет РНР является бесплатным. Самую новую версию можно в любой момент
выгрузить из сайта http: / / www. php. net, причем совершенно бесплатно.
Простота изучения РНР
Синтаксис РНР основан на других языках программирования, в первую очередь
на С и Perl. Если вы уже знакомы с С, Perl или С-подобным языком, таким как C++ или
Java, то почти сразу сможете эффективно использовать РНР.
Поддержка объектно-ориентированного программирования
Версия РНР 5 обладает хорошо спроектированными возможностями объектно-
ориентированного программирования. Если ранее вы сталкивались с языками напо-
добие Java или C++, вы обнаружите очень похожие характеристики и в РНР 5 (в том
числе и синтаксис), среди которых наследование, приватные и защищенные атрибу-
ты и методы, абстрактные классы и методы, интерфейсы, конструкторы и деструкто-
ры. Вы даже обнаружите такие менее общие свойства, как встроенная функциональ-
ность итераций. Часть из упомянутых функций была доступна в версиях РНР 3 и
РНР 4, однако версия РНР 5 отличается более полной поддержкой объектно-
ориентированного программирования.
Переносимость
РНР можно использовать под управлением множества различных операционных
систем. PHP-код можно разрабатывать в среде таких бесплатных Unix-подобных опе-
рационных систем, как Linux и FreeBSD, коммерческих версий Unix типа Solaris и
IRIX или различных версий Microsoft Windows.
Хорошо написанный код, как правило, будет работать без каких-либо изменений в
различных средах, в которых установлен пакет РНР.
30
Введение
Исходный код
Пользователь имеет доступ к исходному коду РНР. В отличие от коммерческих за-
крытых программных продуктов, если нужно что-либо изменить или добавить к язы-
ку, это всегда можно сделать.
Не следует ждать, пока компания-изготовитель выпустит исправления. Также нет
необходимости беспокоиться о том, что изготовитель покинет рынок или перестанет
поддерживать продукт.
Доступность поддержки
Zend Technologies (www.zend.com) — компания, создавшая лежащий в основе
РНР механизм, — финансирует дальнейшее развитие РНР за счет предоставления
поддержки и разработки сопровождающего программного обеспечения на ком-
мерческой основе.
Что нового в версии РНР 5.0?
Скорее всего, вы перешли на РНР 5 с одной из версий РНР 4.x. Как и можно
было ожидать, в РНР 5 было внесено множество существенных изменений. Ме-
ханизм Zend, лежащий в основе РНР, был полностью переписан. Ниже представ-
лены наиболее значимые нововведения.
Улучшенная поддержка объектно-ориентированного программирования, по-
строенная на основе новой объектной модели (см. главу 6).
Механизм исключений, обеспечивающий масштабируемую и легко реализуе-
мую обработку ошибок (см. главу 7).
SimpleXML для простой обработки XML-данных (см. главу 33).
В число других изменений входит перенос некоторых расширений из стандарт-
ной установки РНР в библиотеку PECL, улучшение поддержки потоков и добавление
SQLLite.
Некоторые преимущества MySQL
К основным конкурентам MySQL относятся системы PostgreSQL, Microsoft SQL
Server и Oracle.
MySQL обладает многими преимуществами, в том числе:
высокой производительностью,
низкой стоимостью,
простотой конфигурирования и изучения,
переносимостью,
доступностью исходного кода,
доступностью поддержки.
Более подробно перечисленные преимущества рассматриваются ниже.
Введение
31
Производительность
MySQL, вне всяких сомнений, работает исключительно быстро. Результаты срав-
нительных тестов производительности, выполненных компанией-изготовителем,
можно посмотреть на Web-странице по адресу:
http://web.mysql.com/benchmark.html
Многие из этих сравнительных тестов показывают, что MySQL работает на не-
сколько порядков быстрее конкурирующих продуктов. В 2002 году журнал eWeek
опубликовал результаты сравнения производительности пяти баз данных, используе-
мых для построения Web-приложений. Лучший результат был разделен между MySQL
и значительно более дорогой системой Oracle.
Низкая стоимость
Пакет MySQL доступен бесплатно в соответствие с лицензией на программное
обеспечение с открытым исходным кодом (Open Source) или, если это необходимо
для приложения, за небольшую сумм}- можно приобрести коммерческую лицензию.
Лицензия необходима в случае, если вы хотите распространять MySQL как часть сво-
его приложения, которое не должно подпадать под действие лицензии Open Source.
Если вы не планируете распространять приложения или пользуетесь свободным про-
граммным обеспечением, в лицензии необходимости нет.
Простота использования
В большинстве современных баз данных используется язык SQL. Если ранее вы рабо-
тали с другими СУРБД, переход к этой системе не должен вызывать какие-либо затрудне-
ния. Установка MySQL столь же проста, как и установка многих аналогичных продуктов.
Пер еносимость
MySQL может использоваться в среде многих UNIX-подобных систем, а также в
среде Microsoft Windows.
Исходный код
Как и в случае РНР, исходный код MySQL можно свободно загружать и изменять.
В большинстве случаев и для большинства пользователей этот момент не является
важным, однако он способствует душевном}- спокойствию, гарантируя стабильность и
безопасность дальнейшей работы.
Доступность поддержки
Далеко не для всех продуктов с открытым исходным кодом предоставляется под-
держка, обучение, консалтинг и сертификация со стороны соответствующих компа-
ний-разработчиков. Тем не менее, все вышеупомянутое в отношении РНР обеспечи-
вается компанией MySQL АВ (www.mysql.com).
Что нового в версии MySQL 5.0?
В число крупных изменений, внесенных в MySQL 5.0, входят:
Хранимые процедуры (см. главу 13).
Поддержка курсоров.
32
Введение
Среди других изменений следует отметить более полную совместимость со стан-
дартом ANSI и улучшения, касающиеся производительности. Если вы продолжаете
пользоваться предыдущей версией сервера MySQL (MySQL 3.x либо MySQL 4.x), воз-
можно, принять решение перейти на новую версию поможет следующий список важ-
ных функциональных возможностей, которые были добавлены в MySQL 5.0:
Поддержка подзапросов.
Типы данных GIS для хранения географических данных.
Усовершенствованная поддержка интернационализации.
Безопасный в отношении транзакций механизм хранения InnoDB, ставший
стандартным.
Кэш запросов MySQL, существенно увеличивающий скорость выполнения по-
вторяющихся запросов, которые часто выдаются Web-приложениями.
Как построена эта книга
Книга разделена на пять основных частей.
В части I, “Использование РНР”, приводится обзор основных составляющих
языка РНР с примерами. Каждый из примеров — не какой-то “игрушечный” код, а об-
разец реальной программы, используемой при построении сайта электронной ком-
мерции. Этот раздел начинается с главы 1, “Введение в РНР”. Если вы уже использо-
вали РНР, можете просмотреть ее очень бегло. Если же вы лишь начинаете
знакомиться с РНР или вообще с программированием, возможно, потребуется изу-
чить эту главу более внимательно. Тем не менее, даже если вы имеете большой опыт
использования РНР предыдущих версий, следует тщательно изучить материал главы
6, “Объектно-ориентированное программирование на РНР”, так как в этой области
были внесены значительные изменения.
В части II, “Использование MySQL”, рассматриваются концепции и особенно-
сти разработки, связанные с использованием систем реляционных баз данных типа
MySQL, применение SQL, подключение базы данных MySQL к внешним приложени-
ям с помощью РНР и дополнительные аспекты технологии MySQL вроде безопасно-
сти и оптимизации.
В части III, “Электронная коммерция и безопасность”, освещаются некоторые
из основных вопросов, связанных с разработкой сайта электронной коммерции при
использовании любого языка программирования. Безопасность является наиболее
важной из них. Затем будет показано, как задействовать РНР и MySQL для аутенти-
фикации пользователей и для безопасного сбора, передачи и хранения информации.
В части IV, “Усовершенствованные технологии РНР”, подробно рассматрива-
ются некоторые из основных встроенных PHP-функций. Мы остановили свой выбор
на тех функциях, которые наверняка окажутся полезными при создании сайта элек-
тронной коммерции. Читатели узнают о взаимодействии с сервером, взаимодействии
с сетью, о генерации изображений, манипулировании датой и временем и о пере-
менных сеанса.
Часть V, “Реальные проекты на РНР и MySQL”, является одной из наиболее ин-
тересных для нас как авторов. В ней исследуются такие практические вопросы, как
управление большими проектами и отладка. Здесь же приводятся примеры проектов,
демонстрирующие возможности и гибкость РНР и MySQL.
Введение
33
Заключение
Мы очень надеемся, что читатели получат такое же удовольствие от прочтения
этой книги и ознакомления с РНР и MySQL, какое получили мы, став применять эти
продукты. Их действительно приятно использовать. Со временем вы сможете примк-
нуть ко многим тысячам разработчиков Web-приложений, использующих эти надеж-
ные, обладающие большими возможностями инструментальные средства для просто-
го и быстрого создания динамических, функционирующих в реальном времени Web-
сайтов.
34
Введение
Использование РНР
Глава 1. Введение в РНР
Глава 2. Сохранение и восстановление данных
Глава 3. Использование массивов
Глава 4. Манипулирование строками и регулярные выражения
Глава 5. Многократное использование кода и создание функций
Глава 6. Объектно-ориентированное программирование на РНР
Глава 7. Обработка исключений
1
Введение в РНР
В этой главе предлагается краткий обзор синтаксиса и конструкций языка РНР.
Если вы уже работаете с РНР, то v вас есть возможность пополнить свои зна-
ния. Если у вас имеется некоторый опыт программирования на С, ASP (Active Server
Pages — Активные серверные страницы) или других языках, полученные знания по-
могут вам существенно увеличить производительность труда.
В процессе изучения этой книги вы освоите работу’ в технологии РНР на много-
численных практических примерах, взятых из нашего опыта построения сайтов сис-
тем электронной коммерции. Зачастую в учебниках по программированию синтаксис
языков программирования объясняется на очень простых примерах. Мы решили от-
казаться от подобной практики. Мы понимаем, что для освоения принципов исполь-
зования языка читателю часто вполне достаточно получить пусть не самую лучшую,
зато работающую программу’, вместо того, чтобы просматривать очередной справоч-
ник по синтаксису и функциям, который ничем не лучше интерактивного руково-
дства.
Поэкспериментируйте с примерами — наберите их с клавиатуры или загрузите из
компакт-диска, внесите в них изменения, разбейте на модули и научитесь снова соби-
рать их в единое целое.
Чтобы узнать, как в РНР используются переменные, операции и выражения, в
этой главе мы начнем с того, что рассмотрим пример интерактивной формы заказа
товаров. В ней мы также узнаем, какие типы переменных существуют в языке РНР, и
ознакомимся с приоритетами операций. На основе вычисления общей суммы заказа
и величины налога будет показано, как получить доступ к переменным формы и как
манипулировать ими.
Затем мы разработаем пример интерактивной формы заказа, используя создан-
ный нами для этих целей сценарий на языке РНР для проверки вводимых данных.
11ри этом мы воспользуемся понятием булевских значений и приведем примеры ис-
пользования операторов if, else, switch и операции ?:.
В заключение, написав несколько строк кода на языке РНР (в дальнейшем, для
краткости, PHP-кода) для генерации повторяющихся HTML-таблиц, мы научимся
пользоваться циклами.
В этой главе рассматриваются следующие основные темы:
Встраивание PHP-кода в HTML-код.
Добавление динамического содержимого.
36
Часть I. Использование РНР
Доступ к переменным формы.
Идентификаторы.
Переменные, объявленные пользователем.
Типы переменных.
Присвоение значений переменным.
Объявление и использование констант.
Область действия переменных.
Операции и приоритеты операций.
Выражения.
Использование функций для работы с переменными.
Принятие решений с помощью операторов if, else и switch.
Выполнение итераций: циклы while, do и for.
Использование РНР
Чтобы можно было работать с примерами, приводимыми в этой главе, да и по
всей книге, нужно иметь доступ к Web-серверу с установленным на нем РНР. Дабы
извлечь для себя максимальную пользу из учебных примеров, вы должны запустить их
и периодически вносить различные изменения. Для этого необходимо иметь в своем
распоряжении испытательный стенд, который обеспечил бы возможность экспери-
ментировать с учебными программами.
Если на вашей машине РНР не установлен, вам придется сделать это самому либо
обратиться за помощью к системному администратору. Инструкции по установке
РНР приведены в приложении А. Все программное обеспечение, необходимое для
установки РНР в среде Unix или Windows, можно найти на компакт-диске, который
прилагается к данной книге.
Пример приложения: “Автозапчасти от Боба”
Одним из наиболее распространенных применений любого языка написания сер-
верных сценариев является обработка HTML-форм. Мы начнем изучение технологии
РНР с реализации формы заказа для вымышленной компании “Автозапчасти от Боба”
(“Bob's Auto Parts”), торгующей, как и понятно из ее названия, запасными частями для
автомобилей. Все исходные тексты учебных примеров, использованных в этой главе,
хранятся в каталоге chapterOl на прилагаемом компакт-диске.
Форма заказа
Программист компании “Автозапчасти от Боба”, работающий на языке HTML,
разработал форму для заказа продаваемых Бобом запчастей. Эта сравнительно про-
стая форма, показанная на рис. 1.1, аналогична множеству других форм, которые
можно встретить в Web практически повсеместно. Боб хотел бы иметь возможность
выяснять, что именно заказал его клиент, какова общая сумма заказа, и какую сумму
налога с продаж ему придется уплатить по этому заказу.
Глава 1. Введение в РНР
37
Рис. 1.1. Исходная форма заказа компании “Автозапчасти от
Боба” фиксирует только названия товаров и их количество
Часть HTML-кода создания этой формы представлена в листинге 1.1.
Следует отметить, что действию (action) формы присвоено имя РНР-сценария.
который будет обрабатывать заказ клиента. (Написание этого сценария будет нашей
следующей задачей.) В общем случае значением атрибута action является LIRL-адрес
(LIniform Resource Locator — унифицированный указатель информационного ресур-
са), который будет загружаться после щелчка пользователем на кнопке Submit (От-
править). Данные, введенные пользователем в форму, отправляются по этому URL-
адресу с помощью метода, указанного в атрибуте method: это либо GET (данные добав-
ляются в конец URL-адреса), либо POST (данные отправляются в виде отдельного со-
общения).
Кроме того, обратите внимание на имена полей формы — tireqty (количество ав-
топокрышек), oilqty (количество бутылок машинного масла) и sparkqty (количест-
во свечей зажигания). Эти имена впоследствии будут использоваться в РНР-сценарии.
В силу данного обстоятельства, прежде чем приступать к написанию РНР-сценария.
полям формы важно присвоить имена, которые несут смысловую нагрузку и к тому же
легко запоминаются. Некоторые HTML-редакторы по умолчанию генерируют имена
полей наподобие field23. Такие имена довольно трудно запомнить. Ваша работа
программиста на РНР существенно упростится, если имена полей будут отражать ха-
рактер данных, вводимых в эти поля.
Возможно, вы захотите адаптировать тот или иной стандарт именования полей,
дабы все имена полей в рамках сайта имели один и тот же формат. Гораздо легче за-
помнить, например, сокращения слов, разделенные символами подчеркивания, ко-
торые несут смысловую нагрузку' пробелов.
Листинг 1.1 order £ о rm. html — HTML-код для базовой формы заказа Боба
<form action="processorder.php" method=post>
ctable border="0">
<tr bgcolor="#cccccc">
38
Часть I. Использование PHP
<td width="150">ToBap</td>
<td width="15">K0BM4ecTB0</td>
</tr>
<tr>
< td>Автопокрышки<Itd>
<td align="center"xinput type="text" name="tireqty" size= "3"
maxlength="3"></td>
</tr>
<tr>
<td>MaiiMHHoe Macno</td>
<td align= "center “xinput type="text" name=11 oilqty" size="3"
maxlength="3"></td>
</tr>
<tr>
<td>CBe4M 3a«MraHMX/td>
<td align="center"xinput type=“text" name="sparkqty" size= "3“
maxlength=”3"></td>
</tr>
<tr>
<td colspan=“2" align="center"xinput type="submit"
value="Отправить заказ"></td>
</tr>
</table>
</form>
Обработка формы
Для обработки формы потребуется написать сценарий, указанный в атрибуте
action дескриптора form, и назвать его processorder .php. Откройте текстовый
редактор и создайте этот файл. Затем введите с клавиатуры следующий код:
<html>
<head>
<title>ABT03an4ac™ от Боба - Результаты 3aK.a3a</title>
</head>
<body>
<М>Автозапчасти от Боба</М>
<Ъ2>Результаты заказа</Ь2>
</body>
</html>
Обратите внимание, что все введенное до сих пор представляет собой обычный
HTML-текст. Теперь в сценарий следует добавить немного простого РНР-кода.
Встраивание РНР в HTML
Под заголовком <h2> файла введите следующие строки:
< ?
echo "<р>3аказ обработан.";
?>
Глава 1. Введение в РНР
39
Сохраните файл и загрузите его в браузер, затем заполните форму и щелкните на
кнопке Отправить заказ. На экране вы получите изображение, похожее на представ-
ленное на рис. 1.2.
Рис. 1.2. Текст, переданный PHP-конструкции echo, отобража-
ется браузером
Обратите внимание, как написанный PHP-код встраивается в обычный HTML-
файл. Попробуйте просмотреть исходный код в браузере. Вы должны увидеть сле-
дующие строки:
<html>
<head>
<Ь1б1е>Автозапчасти от Боба - Результаты заказа</б1б1е>
</head>
<body>
<Ь1>Автозапчасти от Боба</М>
<h2>Результаты заказа</Ь2>
<р>3аказ обработан. </px/body>
</html>
Мы не обнаруживаем ни одной строки исходного PHP-кода. Это объясняется тем,
что интерпретатор РНР просмотрел сценарий и заменил его соответствующими
строками вывода. Это означает, что из PHP-кода можно построить чистый HTML-
код, допускающий просмотр в любом браузере, — другими словами, применяемый
пользователем браузер совсем не обязан понимать РНР.
Этот пример служит хорошей иллюстрацией концепции серверных сценариев.
PHP-код интерпретируется и выполняется на Web-сервере, в отличие от JavaScript и
других технологий клиентской стороны, которые интерпретируются и выполняются
в среде Web-браузера на машине пользователя.
40
Часть I. Использование РНР
Теперь код, помещенный в этот файл, относится к следующим четырем типам:
HTML;
дескрипторы РНР;
операторы РНР;
пробелы.
Также можно добавлять и комментарии.
Большинство строк в приведенном выше примере — всего лишь простой HTML-код.
Использование РНР-дескрипторов
PHP-код в предыдущем примере начинается с конструкции <?php и завершается
конструкцией ?>. Это аналогично всем HTML-дескрипторам, поскольку все они на-
чинаются с символа “меньше” (<) и завершаются символом “больше” (>). Символы
<?php и ?> называются PHP-дескрипторами, поскольку они указывают Web-серверу,
где начинается и где заканчивается PHP-код. Любой текст, находящийся между этими
дескрипторами, интерпретируется как PHP-код. Любой текст за пределами этих де-
скрипторов трактуется как обычный HTML-код. РНР-декрипторы позволяют перейти
с кода HTML на другой код.
Существуют различные стили дескрипторов. Рассмотрим этот вопрос несколько
подробнее.
Стили РНР-дескрипторов
Фактически существует четыре различных стиля РНР-дескрипторов. Все приве-
денные ниже фрагменты кода эквивалентны.
XML-стиль
<?php echo '<р>3аказ обработан.</р>'; ?>
Этот стиль дескрипторов используется в данной книге и является наиболее
предпочтительным в РНР. Администратор сервера не имеет возможности его
отключить, и по этой причине он гарантированно доступен на всех серверах,
что особенно в ситуациях, когда вы разрабатываете приложения, рассчитан-
ные на выполнение в различных средах. Такой стиль дескрипторов может ис-
пользоваться в документах XML (Extensible Markup Language — расширяемый
язык разметки). Если вы планируете обрабатывать XML-документы на своем
сайте, вам следует отдать предпочтение именно этому стилю дескрипторов.
Сокращенный стиль
<? echo '<р>3аказ обработан.</р>'; ?>
Этот стиль дескрипторов является самым простым и соответствует стилю ин-
струкций обработки языка SGML (Standard Generalized Markup Language —
стандартный обобщенный язык разметки). Чтобы использовать этот тип деск-
рипторов (который к тому же и наиболее краткий для ввода с клавиатуры), вы
должны либо включить переменную short_open_tags в файле конфигурации,
либо скомпилировать РНР с включенными сокращенными дескрипторами. Бо-
лее подробная информация по использованию данного стиля представлена в
Глава 1. Введение в РНР
41
приложении А. Тем не менее, применение этого стиля не рекомендуется, так
как несмотря на то, что данный стиль включен по умолчанию, системные ад-
министраторы часто его отключают, поскольку он может конфликтовать с
объявлениями XML-документов.
SCRIPT-стиль
<script language='php'> echo '<р>3аказ обработан.</p>'; </script>
Этот стиль дескрипторов является самым длинным; он знаком тем, кому при-
ходилось использовать JavaScript или VBScript. Его можно применять при ра-
боте в редакторе HTML, когда возникают проблемы с другими стилями деск-
рипторов.
ASP-стиль
<% echo '<р>3аказ обработан.</р>'; %>
Аналогичный стиль дескриптора используется в технологии ASP (Active Server
Pages — активные серверные страницы). Он может применяться, если установ-
лен конфигурационный параметр asp_tags. Использованию этого стиля отда-
ется предпочтение в случае работы с редактором, ориентированным на ASP
или ASP.NET, либо если вы пишете программы на ASP или ASP.NET.
Операторы РНР
Действия, которые должен выполнить интерпретатор РНР, задаются операто-
рами РНР, помещаемыми между открывающим и закрывающим дескрипторами.
В рассматриваемом примере используется только один тип оператора;
echo '<р>3аказ обработан.</р>';
Вы, должно быть, уже догадались, что оператор echo выполняет очень простое дей-
ствие — он выводи! (или печатает) в окне браузера переданную ему строку. На рис.
1.2 видно, что в результате в окне браузера отображается текст "Заказ обработан. ".
Обратите внимание на то, что в конце оператора echo находится точка с запятой.
Она используется для разделения PHP-операторов подобно тому', как точка служит
для разделения предложений в обычном языке. Тем, кто ранее программировал на
языке С или Java, подобное применение точки с запятой должно показаться вполне
естественным.
Пропуск точки с запятой — довольно расп[юстраненная синтаксическая ошибка, ко-
торую очень легко допустить. В то же время ее столь же просто выявить и исправить.
Пробелы
Пустые символы, такие как пустые строки (возврат каретки), пробелы между сло-
вами и символы табуляции, образуют категорию пробельных. Вам, должно быть. уже
известно, что браузеры игнорируют пробельные символы в HTML-коде. Механизм
РНР действует точно так же. Рассмотрим два следующих фрагмента HTML-кода:
<Ы>Добро пожаловать в компанию "Автозапчасти от Боба" !</hl><p>4io бы вы
хотели заказать сегодня?</р>
и
42
Часть I. Использование РНР
<Ь1>Добро пожаловать в
компанию "Автозапчасти от Боба"!</hl>
<р>Что бы вы хотели
заказать сегодня? </р>
Эти два фрагмента HTML-кода генерируют один и тот же вывод, поскольку с точ-
ки зрения браузера они идентичны. Пробелы в HTML-кодах использовать можно и
нужно, поскольку они повышают удобочитаемость HTML-кода. То же можно сказать
и в отношении РНР. Пробелы между PHP-операторами не требуются, однако разме-
щение каждого оператора в отдельной строке существенно облегчает чтение кода.
Например, фрагменты
echo 'приветствуем';
echo 'на нашем сайте';
и
echo 'приветствуем';echo 'на нашем сайте';
эквивалентны, но первую версию понять гораздо проще.
Комментарии
Понятие комментария имеет следующий смысл: комментарии к коду служат пояс-
нениями для людей, разбирающихся с текстом программы. Комментарии использу-
ются для описания назначения соответствующего сценария, для информации о раз-
работавших его программистах, для пояснения, почем}’ они написали его именно так,
а не иначе, для указания даты его последнего изменения и прочей полезной инфор-
мации. Как правило, комментариями снабжаются все PHP-сценарии, за исключени-
ем, возможно, самых простых.
Интерпретатор РНР игнорирует любой текст, помещенный в комментарий. По
существу, синтаксический анализатор РНР попросту пропускает комментарии, кото-
рые для него равнозначны пробелам.
РНР поддерживает комментарии в стилях С, C++ и сценариев оболочки.
Ниже показано как выглядит многострочный комментарий в стиле С, который
может находиться в начале РНР-сценария:
/* Автор: Боб Смит
Дата последнего изменения: 3 июня
Этот сценарий обрабатывает заказы клиентов.
*/
Многострочные комментарии должны начинаться с символов /* и завершаться
символами */. Как и в языке С, многострочные комментарии не могут быть вложен-
ными.
Можно также использоваться однострочными комментариями в стиле C++:
echo '<р>3аказ обработан.</р>'; // Начало вывода заказа
или в стиле сценариев оболочки:
echo '<р>3аказ обработан.</р>'; # Начало вывода заказа
Глава 1. Введение в РНР
43
При использовании обоих этих стилей все, что следует за символом комментария
(# или //) вплоть до конца строки или до завершающего PHP-дескриптора, в зависи-
мости от того, что встретится раньше, рассматривается как комментарий.
В показанной ниже строке кода текст перед закрывающим дескриптором (это
комментарий) является частью комментария. В то же время текст а это — уже не
комментарий трактуется как HTML-код, поскольку он находится после закрывающего
дескриптора.
// это комментарий ?> а это - уже не комментарий
Добавление динамического содержимого
До сих пор мы не использовали РНР для выполнения каких-либо действий, кото-
рые нельзя было бы реализовать с помощью обычного HTML.
Основная побудительная причина применения языка написания серверных сце-
нариев — желание предоставить пользователям сайт с динамическим содержимым.
Это важное применение РНР, ибо содержимое, изменяющееся в соответствии с по-
требностями пользователя или с течением времени, заставляет посетителей вновь и
вновь возвращаться на сайт. РНР позволяет легко организовать это.
Начнем с рассмотрения простого примера. Заменим PHP-код в файле processor-
der . php на приведенный ниже:
<?
echo 1<р>3аказ обработан в ';
echo date('H:i, JS F');
echo '</₽>';
В этом коде используется встроенная PHP-функция date (), которая сообщает
клиенту дату и время обработки его заказа. Значения будут меняться при каждом вы-
полнении сценария. Вывод, полученный в результате одного из таких запусков рас-
сматриваемого сценария, показан на рис. 1.3.
Вызов функций
Посмотрите, как вызывается функция date() - Это общая форма вызова функции.
РНР имеет обширную библиотеку функций, которыми вы можете пользоваться при
разработке Web-приложений. Большинству этих функций нужно передавать некото-
рые данные, чтобы они возвращали соответствующие данные в качестве результатов.
Вызов функции имеет следующий вид:
date('Н:i, j S F')
Обратите внимание на то, что строка, передаваемая функции (текстовые данные),
заключена в круглые скобки. Это значение называется аргументом или параметром
функции. Аргументы представляют собой входные значения, которые используются
функцией для вывода соответствующих результатов.
44
Часть I. Использование РНР
С Автозапчасти от Боба - и р* - vtadfc’
а и
Обновить
JnjxJ
Автозапчасти от Боба
Результаты заказа
Заказ обработан в 15 24, 14th June
В
’ Файл Правка Вод Переход Закладки инструменты €«но Справка Debug QA
=* hap7/tocalhosVphpniysql3e/chapter014jrocessorc!e *! Поиск}
Назад Обновить ——-з •
Рис. 1.3. PHP-функция date () возвращает форматированную
строку даты
Использование функции date ()
Аргумент, передаваемый функции date (), должен быть строкой формата, кото-
рый определяет требуемый стиль вывода. Каждая буква в строке представляет часть
строки даты и времени суток. Н представляет часы в 24-часовом формате, i — минуты
с ведущим нулем, когда он необходим, j — день месяца без ведущего нуля, S представ-
ляет обычный суффикс (в данном случае “th”), a F —полное название месяца.
(Полный список форматов, поддерживаемых функцией date (), можно найти в
главе 20.)
Доступ к переменным формы
Весь смысл использования формы заказа заключается в получении информации о
заказе клиента. Получение подробной информации о том, что клиент ввел с клавиа-
туры, реализуется в РНР очень просто, тем не менее, точный метод зависит от выбо-
ра версии РНР и от установок в вашем файле php. ini.
Переменные формы
Внутри PHP-сценария к каждому из полей формы можно получить доступ как к
PHP-переменной, которая имеет то же имя, что и поле формы. В языке РНР пере-
менные легко распознать, так как все они начинаются со знака доллара ($). (Распро-
страненная ошибка связана как раз с пропуском знака доллара.)
Вы можете получить доступ к содержимому поля tireqty следующими способами:
$tireqty // короткий стиль
$_POST['tireqty'] // средний стиль
$HTTP_POST_VARS['tireqty'] // длинный стиль
Глава 1. Введение в РНР
45
В этом примере, как и на протяжении всей книги, мы используем средний стиль
для ссылок на переменные формы (то есть $_POST [' tireqty' ]), ио для простоты ис-
пользования мы создаем короткие версии применения. (Это рекомендуемый подход,
начиная с версии РНР 4.2.0.)
При разработке своего собственного кода вы можете избрать другой подход, од-
нако выбор должен быть обоснованным, поэтом}- мы сейчас рассмотрим все возмож-
ные методы.
Выражаясь несколькими словами:
Короткий стиль ($tireqty) удобен в работе, однако он требует включения
конфигурационной настройки register_globals. Включена она или нет по
умолчанию, зависит от используемой версии РНР. Во всех версиях, начиная с
РНР 4.2.0, настройка register_globals по умолчанию отключена. В более
ранних версиях она была, наоборот, включена, поэтому большинство про-
граммистов на РНР применяли именно короткий стиль. С момента отключе-
ния короткого стиля возникало немало коллизий. Кроме того, этот стиль бла-
гоприятствует появлению ошибок, которые делают ваш программный код
менее безопасным, что и привело к тому, что короткий стиль использовать не
рекомендуется.
Средний стиль ($_POST[ ' tireqty’ ]) теперь является рекомендованным под-
ходом. Он достаточно удобный, однако был введен в употребление только в
версии РНР 4.1.0, поэтому не будет работать на более старых версиях.
Длинный стиль ($HTTP_POST_VARS [' tireqty']) представляет собой наиболее
подробную форму- записи. Следует отметить, что данный стиль признан уста-
ревшим и, скорее всего, в долгосрочной перспективе он вообще выйдет из
употребления. Длинный стиль используется для достижения наибольшей пе-
реносимости, однако сейчас может быть отключен с помощью директивы
конфигурации register_long_arrays. что приводит к увеличению производи-
тельности.
При использовании короткого стиля имена переменных в сценарии ничем не от-
личаются от имен полей в HTML-форме. Вам не надо объявлять эти переменные или
предпринимать какие-либо действия по созданию этих переменных в сценариях. Они
фактически передаются в ваш сценарий так, как передаются аргументы в функцию.
Когда вы пользуетесь этим стилем, вы манипулируете такими переменными как, на-
пример, $tireqty. Поле tireqty формы создает в сценарии, выполняющем обработ-
ку’, переменную $ tireqty.
Подобное удобство доступа к переменным может показаться довольно-таки при-
влекательным, однако перед тем как просто включить register_globals, неплохо бы
разобраться с причинами, по которым команда разработчиков РНР приняла решение
по умолчанию отключить register_globals.
Очень удобно иметь прямой доступ к переменным, однако он создает условия для
программных ошибок, которые могут скомпрометировать безопасность ваших сце-
нариев. При таком способе автоматического преобразования переменных формы в
глобальные переменные, подобные рассматриваемым, нельзя отделить созданные
вами переменные от непроверенных переменных, которые поступают непосредст-
венно от пользователя.
46
Часть I. Использование РНР
Если вы не позаботились о том, чтобы присвоить начальные значения, то пользо-
ватели ваших сценариев смогут передавать переменные и значения в виде перемен-
ных формы, которые перемешиваются с вашими собственными переменными. Если
вы выбираете более удобный короткий стиль доступа к переменным, то должны со-
блюдать осторожность при назначении своим собственным переменным начальных
значений.
Средний стиль предусматривает считывание переменных форм из массивов
$_POST, $_GET и $_REQUEST. Подробное описание всех переменных форм содержится
либо в массиве $_POST, либо в массиве $_GET. Какой массив используется, зависит,
соответственно, от того, какой метод был выбран для передачи формы — POST или
GET. Кроме того, все данные, передаваемые посредством метода POST или GET, дос-
тупны через массив $_REQUEST.
Если форма была отправлена с помощью метода POST, то данные, помещенные в
поле tireqty, будут сохранены в $_POST [ ' tireqty' ]; если же форма была передана с
помощью метода GET — то в $_GET [' tireqty' ]. И в том и в другом случае данные будут
доступны в $_REQUEST [ ' tireqty' ].
Эти массивы относятся к категории так называемых суперглобальных. Мы вер-
немся к рассмотрению суперглобальных переменных, когда будем исследовать об-
ласть действия переменных.
Если вы работаете в более старых версиях РНР, вы не сможете получить доступ к
массивам $_POST и $_GET. До появления версии 4.L0 эта информация хранилась в
массивах $HTTP_POST_VARS и $HTTP_GET_VARS. Мы называем это длинным стилем. Как
упоминалось ранее, этот стиль признан устаревшим и пользоваться им не рекоменду-
ется. В этом стиле нет эквивалента массиву $_REQUEST.
Если вы используете длинный стиль, вы можете получить доступ к ответу пользо-
вателя через $HTTP_POST_VAR['tireqty'] или $HTTP_GET_VARS['tireqty'].
Примеры, рассмотренные в этой книге, были протестированы в версии РНР 5.0,
при этом отмечались случаи несовместимости с версиями РНР, предшествующими
4.1.0. Мы настоятельно рекомендуем использовать там, где это возможно, текущую на
данный момент версию.
Рассмотрим еще один пример. Поскольку имена переменных в длинном и среднем
стилях иногда получаются несколько громоздкими и основываются на типах, извест-
ных под названием массивов, изучение которых мы отложим до главы 3, мы начнем с
создания упрощенных копий.
Для копирования значения одной переменной в другую служит операция при-
сваивания, для обозначения которой в языке РНР используется знак равенства (=).
Приведенная ниже строка кода создает новую переменную с именем $ tireqty и ко-
пирует в нее содержимое $HTTP_POST_VAR [' tireqty1 ].
$tireqty = $HTTP_POST_VAR['tireqty'];
Поместите показанный далее блок кода в начато сценария обработки формы. Все
другие сценарии, рассмотренные в этой книге, которые обрабатывают данные форм,
содержат в начале подобные блоки. Так как этот сценарий не генерирует никаких
выходных данных, нет никакой разницы, поместите ли вы его выше или ниже деск-
риптора <html> и других HTML-дескрипторов, с которых начинается ваша страница.
Глава 1. Введение в РНР
47
Обычно мы размещаем этот блок в самом начале сценария, чтобы потом его было
легче найти.
<?php
// создать короткие имена переменных
$tireqty = $HTTP_POST_VAR['tireqty'];
$oilqty = $HTTP_POST_VAR['oilqty'];
$sparkqty= $HTTP_POST_VAR [' sparkqty' ] ;
Этот код создает три переменных $tireqty, $oilqty и $sparkqty и помещает в
них данные, которые были переданы с помощью метода POST формы. Чтобы этот
сценарий выполнял сколько-нибудь заметные действия, вставьте следующие строки в
нижнюю часть РНР-сценария:
echo 1<р>Список вашего заказа: </р>';
echo $tireqty. ' автопокрышек<Ьг />';
echo $oilqty. ' бутылок с маслом<Ьг />';
echo $sparkqty. ' свечей зажиганижЪг />';
Вы, должно быть, уже заметили, что на данной стадии мы не проверяем содержи-
мого переменных с тем, чтобы воспрепятствовать вводу бессмысленных данных в
каждое поле формы. Попытайтесь намеренно ввести бессмысленные данные и по-
смотрите, что из этого получится. После того, как вы ознакомитесь с содержимым
остальной части этой главы, у вас, скорее всего, появится желание добавить в разра-
батываемый сценарий код, проверяющий корректность данных.
Если теперь загрузить модифицированный файл в браузер, выходные данные сце-
нария должны быть подобны показанным на рис. 1.4. Фактические значения, разуме-
ется, зависят от того, какую информацию вы введете в форму.
Рис. 1.4. Значения переменных формы, которые вводят пользо-
ватели, легко доступны в сценарии processorder. php
48
Часть I. Использование РНР
Несколько интересных особенностей этого примера, на которые следует обра-
тить внимание, описаны в следующих подразделах.
Конкатенация строк
В сценарии оператор echo применялся для вывода значений, введенных пользова-
телем в каждое из полей формы, за которыми следовал некоторый пояснительный
текст. Внимательно присмотритесь к операторам echo, и вы заметите, что между
именем переменной и следующим за ним текстом находится точка (.), например:
echo $tireqty. ' автопокрышексЪг />';
Эта точка есть ни что иное, как операция конкатенации строк, которая использу-
ется для объединения строк (фрагментов текста) в единый текст. Она будет часто
применяться при пересылке вывода в браузер с помощью echo. Эта операция позво-
ляет избегать записи нескольких операторов echo.
Каждую переменную, отличную от переменной типа массива, можно поместить в
двойные кавычки, после чего применить к ней оператор echo. (Массивы представля-
ют собой более сложный тип переменных. Вопросы комбинирования строк и масси-
вов рассматриваются в главе 4.) Например:
echo "$tireqty автопокрышек<Ьг />";
Этот оператор эквивалентен первом}-. Оба формата допустимы, и какой из них
употребить — это дело сугубо личного вкуса. Такой процесс замены имени перемен-
ной ее содержимым известен как вставка. Обратите внимание, что при вставке долж-
ны применяться только двойные кавычки. Нельзя помещать имена переменных в
одинарные кавычки в подобных случаях. Выполнение следующей строки кода:
echo '$tireqty автопокрышек<Ьг />’;
приведет к передаче в браузер строки "$tireqty автопокрышек<Ьг />". Если имя пе-
ременной заключено в двойные кавычки, то имя переменной заменяется ее значени-
ем. Если имя переменной или какой-либо другой текст заключен в одинарные кавыч-
ки. то они передаются без изменений.
Переменные и литералы
Переменные и строки, конкатенацию которых мы осуществляем в каждом из опе-
раторов echo, имеют разную природу. Переменные — это символы, применяемые для
обозначения данных. Строки — это, по сути, данные. Когда мы употребляем фрагмен-
ты неструктурированных данных в программе, подобной рассматриваемой, мы назы-
ваем их литералом, чтобы отличить их от переменной. $tireqty — это переменная,
то есть символ, который представляет введенные клиентом данные. С другой сторо-
ны, ’ автопокрышек<Ьг />' — это литерал. Он представляет самого себя.
Ну что ж, мы почти закончили с этим вопросом. Помните второй пример, приве-
денный выше в разделе? РНР заменяет в этой строке имя переменной Stireqty зна-
чением, которое хранится в этой переменной.
Не забывайте, что в РНР существует два вида строк — с двойными кавычками и
одинарными кавычками. РНР будет предпринимать попытки вычислить значения
строк, заключенных в двойные кавычки, что приведет к результатам, которые рас-
Глава 1. Введение в РНР
49
сматривались выше. Строки, заключенные в одинарные кавычки, трактуются как
обычные литералы.
Недавно для указания строк появился еще и третий способ. В РНР 4 был введен
heredoc-синтаксис («<), хорошо знакомый программистам на языке Perl. Этот син-
таксис позволяет определять длинные строки аккуратно, указывая маркер конца
строки, который и будет использоваться для завершения строки. В представленном
ниже примере определяется и выводится длинная строка:
echo «ctheEnd
line 1
line 2
line 3
theEnd
Лексема theEnd выбрана совершенно произвольно. При ее выборе должно лишь
гарантироваться, что она нигде не встречается в тексте. Для завершения heredoc-
строки необходимо поместить в начале новой строки лексему конца строки, heredoc-
строки допускают вставку переменных, подобно строкам в двойных кавычках.
Идентификаторы
Идентификаторы представляют собой имена переменных. (Имена функций и
классов — это тоже идентификаторы; функции и классы рассматриваются в главах 5
и 6.) Использование идентификаторов регламентируется следующими простыми
правилами:
Идентификаторы могут иметь любую длину и состоять из букв, цифр и симво-
лов подчеркивания.
Идентификаторы не могут начинаться с цифры.
В РНР идентификаторы чувствительны к регистру символов. Идентификато-
ры $tireqty и $TireQty отнюдь не равнозначны. Попытка использования
строчных символов вместо прописных и наоборот — очередная часто встре-
чающаяся ошибка программирования. Исключение из этого правила состав-
ляют встроенные РНР-функции — их имена могут быть представлены в любом
регистре.
Переменные могут иметь те же имена, что и встроенные функции. Однако это
может привести к путанице, а посему подобных ситуаций следует избегать.
Нельзя также создавать функции, имена которых совпадают с именами других
функций.
Переменные, объявленные пользователем
В дополнение к переменным, передаваемым из HTML-формы, вы можете объяв-
лять и использовать свои собственные переменные.
Одна из особенностей РНР заключается в том, что переменные не обязательно
объявлять до того, как вы будете ими пользоваться. Переменная создается в момент
первого присвоения ей значения; подробнее об этом мы поговорим в следующем
разделе.
50
Часть I. Использование РНР
Присвоение значений переменным
Значения переменным присваиваются с помощью операции присваивания =. На
сайте компании “Автозапчасти от Боба" требуется подсчитать общее количество еди-
ниц товара и общую сумму оплаты. Для хранения этих чисел имеет смысл создать две
переменных. Для начала они инициализируются нулевыми значениями.
Добавьте следующие строки в нижнюю часть вашего РНР-сценария:
Stotalqty = 0;
Stotalamount = 0.00;
Каждая из двух приведенных строк создает переменную и присваивает ей лите-
ральное значение. Переменным можно присваивать также значения других перемен-
ных, как показано в примере ниже:
Stotalqty = 0;
Stotalamount = $totalqty;
Типы переменных
Тип переменной характеризуется видом хранящихся в ней данных. РНР предлага-
ет постоянно растущий набор типов данных. Различные данные могут храниться в
переменных различных типов.
Типы данных РНР
РНР поддерживает следующие базовые типы данных:
Integer (целый) — используется для представления целых чисел.
Float, также называемый double (двойной точности), — используется для
представления действительных чисел.
String (строковый) — используется для представления строк символов.
Boolean (булевский) — используется для хранения значений true (истина) и
false (ложь).
Array (массив) — используется для хранения нескольких элементов данных (см.
главу 3).
Object (объект) — используется для хранения экземпляров классов (см. главу 6).
Доступны также и два специальных типа — NULL и resource (ресурс). Перемен-
ные, которым не присвоены конкретные значения, которые не определены или
принимают значение NULL, относятся к типу NULL. Некоторые встроенные функции
(такие как функции работы с базами данных) возвращают переменные ресурсного
типа. Такие переменные представляют внешние ресурсы (например, соединения с
базами данных). Можно с достаточной уверенностью утверждать, что напрямую
манипулировать переменными ресурсного типа вам не придется, тем не менее, они
часто возврапфются одними функциями и передаются в качестве параметров в дру-
гие функции.
Глава 1. Введение в РНР
51
РНР поддерживает также типы pdf doc и pdf info, если установлена поддержка об-
работки PDF-документов (Portable Document Format — формат переносимых доку-
ментов). Использование PDF-формата в РНР рассматривается в главе 30.
Преимущества использования типов
Язык РНР является весьма слабо типизированным. В большинстве языков про-
граммирования переменные могут хранить данные только одного типа, и этот тип
должен быть объявлен прежде, чем переменную можно будет использовать, как это
имеет место, например, в языке С. В РНР тип переменной определяется типом при-
своенного ей значения.
Например, при создании переменных Stotalqty и Stotalamount их начальные
типы были определены следующим образом:
$tctalqty = 0;
Stotalamount = 0.00;
Поскольку переменной $totalqty было присвоено целочисленное значение 0, эта
переменная теперь имеет тип integer. Аналогично, переменная Stotalamount имеет
тип float.
Как ни странно, но в сценарий вполне можно поместить такую строку':
Stotalamount = 'Добро пожаловать';
Теперь переменная Stotalamount имеет тип string. РНР в любой момент време-
ни изменяет тип переменной в соответствии с хранящимися в ней данными.
Подобная возможность явного изменения типов на лету может оказаться исклю-
чительно полезной. Помните, что РНР “автоматически” распознает тип данных, по-
мещаемых в переменные. РНР возвращает данные именно того типа, который был
назначен переменной.
Приведение типов
С помощью механизма приведения типов можно переводить переменную или
конкретное значение в другой тип. Приведение выполняется так же, как в языке С.
Для этого достаточно просто перед переменной, тип которой вы хотите преобразо-
вать, поместить в круглых скобках временный тип.
Например, мы можем объявить две использованные выше переменные, применив
при этом механизм приведения типов.
Stotalqty = 0;
Stotalamount = (float)Stotalqty;
Вторая строка означает: “Взять значение, хранящееся в переменой Stotalqty,
интерпретировать его как значение типа float и сохранить в переменной
Stotalamount”. Переменная Stotalamount получит тип float. Приведение типов не
меняет тип исходной переменной, поэтому типом переменной Stotalqty остается
integer.
52
Часть I. Использование РНР
Переменные переменных
РНР предоставляет в распоряжение разработчиков еще один тип переменных —
так называемые переменные переменных. Переменные переменных позволяют ди-
намически менять имена переменных.
Как вы сами можете убедиться, РНР допускает очень большую свободу в этой об-
ласти — все языки разрешают изменять значение переменной, но лишь некоторые
позволяют изменять тип переменной и уж совсем немногие — имя переменной.
В основу этой возможности положена идея использования значения одной пере-
менной в качестве имени другой. Например, можно было бы определить так:
$varname = "tireqty";
Затем вместо $ tireqty можно использовать $$varname, например:
$$varname = 5;
Это в точности эквивалентно:
$tireqty = 5;
Данная особенность может показаться несколько запутанной, однако позже мы
еще вернемся к практическому использованию этой возможности. Вместо того чтобы
перечислять все переменные и использовать каждую переменную формы по отдель-
ности, можно зарезервировать еще одну переменную и организовать автоматическую
обработку всех переменных в цикле. Пример, иллюстрирующий такой подход, при-
веден в разделе, посвященном циклам for.
Объявление и использование констант
Как вы уже могли убедиться ранее, значение, хранящееся в переменной, можно
без труда изменить. Наряду с этим, в РНР допускается также объявление констант.
Как и переменная, константа хранит значение, но ее значение устанавливается раз и
навсегда, и не может изменяться ни в какой части сценария.
В нашем примере приложения цены всех единиц товаров, выставленных на про-
дажу, можно сохранить в виде констант. Такие константы можно определять с помо-
щью функции def ine:
define!'TIREPRICE', 100);
define!'OILPRICE', 10);
define!'SPARKPRICE', 4);
Добавьте эти строки в сценарий. Теперь вы имеете три константы, которые мож-
но использовать при расчете общей суммы заказа.
Вы должны были заметить, что все имена констант записываются прописными
буквами. Данное соглашение заимствовано из языка С; благодаря ему различать пе-
ременные и константы визуально легче. Соблюдать это соглашение вовсе не обяза-
тельно, тем не менее, следует помнить, что оно существенно упрощает чтение и со-
провождение кода.
Важное различие между константами и переменными состоит в том, что при об-
ращении к константе перед ней не нужно ставить знака доллара. Если вам необходи-
мо воспользоваться значением константы, указывайте только ее имя. Например, для
Глава 1. Введение в РНР
53
вывода на экран значения одной из созданных выше констант применяется следую-
щий код:
echo TIREPRICE;
Наряду с константами, определенными пользователем, РНР устанавливает боль-
шое количество собственных констант. Эти константы легко просмотреть, если вы-
звать функцию phpinfо():
phpinfо();
Упомянутая функция выводит на экран список предопределенных переменных и
констант РНР, а также другую полезную информацию. Некоторые из этих элементов
этого списка будут рассматриваться по мере изложения материала.
Область действия переменных
Термин область действия (scope) относится к тем разделам сценария, внутри кото-
рых возможен доступ к некоторой конкретной переменной, иначе говоря, область,
из любого места которой видна эта переменная. В РНР используются следующих
шесть базовых правил определения области действия:
Встроенные суперглобальные переменные видны из любого места сценария.
Константы , как только они объявлены, всегда видимы глобально, то есть могут
использоваться как внутри, так и все функций.
Глобальные переменные, объявленные в сценарии, видны в любом месте сце-
нария, но не внутри функций.
Переменные, использованные внутри функций, которые объявлены как гло-
бальные, ссылаются на глобальные переменные с теми же именами.
Переменные, созданные внутри функции и объявленные как статические, не-
видимы за пределами функции, однако они сохраняют свои значения между
двумя вызовами этой функции.
Переменные, созданные внутри функции, являются локальными по отноше-
нию к своей функции и прекращают свое существование после завершения
функции.
В версии РНР 4.1 и выше массивы $_GET и $_POST и ряд других специальных пере-
менных подчиняются своим собственным правилам, определяющим их области дей-
ствия. Они принадлежат к категории суперглобальных переменных и видимы везде,
как внутри функций, так и за их пределами.
Ниже представлен полный список суперглобальных переменных:
$GLOBALS. Массив всех глобальных переменных. Подобно ключевому слову
global, этот массив позволяет получать доступ к глобальным переменным
внутри функции, например, $GLOBALS [ 'myvariable ' ].
$_SERVER. Массив переменных среды сервера.
$_GET. Массив переменных, переданных в сценарий посредством метода GET.
$_POST. Массив переменных, переданных в сценарий посредством метода POST.
54
Часть I. Использование РНР
$_С00К1Е. Массив cookie-переменных.
$_FILES. Массив переменных, относящихся к загрузке файлов.
$_ENV. Массив переменных окружения.
$_REQUEST. Массив пользовательского ввода, включая содержимое массивов
$_GET, $_POST и $_СООК1Е.
$_SESSION. Массив переменных сеанса.
На протяжении книги мы будем по мере необходимости обращаться к этим типам
данных.
Понятие области действия будет подвергнуто более подробному анализу во время
изучения функций. Пока лишь отметим, что все переменные, которые мы использу-
ем, по умолчанию являются глобальными.
Использование операций
Операции — это символы, которые используются для манипуляции значениями и
переменными за счет выполнения над ними той или иной операции. Некоторые из
этих операций нам потребуются для вычисления общей суммы заказа клиента и раз-
мера налога на этот заказ.
Ранее уже упоминались две операции: присваивания (=) и конкатенации строк (.).
В следующих разделах мы рассмотрим полный список операций.
В общем случае операции могут выполняться над одним, двумя и тремя аргумен-
тами. причем большинство из них выполняется над двумя аргументами. Например,
операция присваивания требует двух аргументов, а именно, адреса ячейки, указывае-
мого слева от символа =, и выражения, указываемого справа от него. Эти аргументы
называются операндами, то есть элементами, над которыми выполняется соответст-
вующая операция.
Арифметические операции
Арифметические операции очень просты — это обычные математические опера-
ции. Арифметические операции перечислены в табл. 1.1.
Таблица 1.1. Арифметические операции РНР
Операция Название Пример
+ Сложение $а + $Ь
Вычитание $а - $ь
* Умножение $а * $Ь
/ Деление $а / $Ъ
% Деление по модулю $а % $Ь
Мы можем сохранить результат любой из этих операций, например:
$result = $а + $Ь;
Глава 1. Введение в РНР
55
Сложение и вычитание имеют традиционный смысл. Результатом их выполнения
является, соответственно, сумма и разность значений, хранящихся в переменных $а
и $Ь.
Символ вычитания (-) можно использовать в качестве унарной операции (то есть
операции, которая выполняется над одним аргументом или операндом) для обозна-
чения отрицательных чисел. Например:
$а = -1;
Умножение и деление также работают обычным образом. Обратите внимание на
использование звездочки вместо традиционного математического символа умно-
жения и наклонной черты вместо обычного символа деления.
Операция деления по модулю возвращает остаток от деления переменной $а на
переменную $Ь. Рассмотрим следующий фрагмент кода:
$а = 27;
$Ь = 10;
$result = $a%$b;
Значение, сохраненное в переменной Sresult. представляет собой остаток от де-
ления 2 7 на 10, то есть 7.
Следует обратить внимание на то, что арифметические операции обычно приме-
няются к целым числам или значениям с двойной точностью. В случае их применения
к строкам РНР предпринимает попытку' выполнить эти операции, преобразуя строки
в числа. Если строка содержит символы “е” или “Е”, то она считается числом в экспо-
ненциальной форме записи и преобразуется в числовое значение float. В противном
случае строка преобразуется в целочисленное значение. РНР выполняет поиск цифр
в начале строки и найденные цифры использует в качестве значения; если в начале
строки цифр нет, то ее значением будет ноль.
Строковые операции
Выше мы сталкивались только с одной строковой операцией — операцией конка-
тенации строк. Ее можно применять для объединения двух строк в одну и сохранения
результата, при этом она имеет много общего с операцией сложения двух чисел.
$а = "Автозапчасти
$Ь = "от Боба";
$result = $a.$b;
Теперь переменная $result содержит строку "Автозапчасти от Боба".
Операции присваивания
Мы уже знакомы с операцией =, основной операцией присваивания. Этот символ
всегда означает операцию присваивания и читается как “устанавливается равным”.
Например:
$totalqty = 0;
Эту строку следует понимать, как “значение переменной $totalqty устанавлива-
ется равным нулю”. Почему именно так, а не иначе, нам станет ясно после того, как
мы рассмотрим операции сравнения далее в этой главе.
56
Часть I. Использование РНР
Значения, возвращаемые операцией присваивания
Как и в случае других операций, в результате выполнения операции присваивания
возвращается некоторое итоговое значение. Если записать:
$а + $Ь
то значением этого выражения будет результат сложения переменных $а и $Ь. Анало-
гично, можно записать:
$а = 0;
Значением всего приведенного выражения будет 0.
В конечном итоге появляется возможность выполнять действия, подобные сле-
дующему:
$Ь = 6 + ($а = 5);
В результате значение переменной $Ь устанавливается равным 11. Это справедли-
во для всех операторов присваивания: значение всего оператора присваивания есть
значение, присвоенное левому операнду.
При написании выражения можно пользоваться круглыми скобками для увеличе-
ния приоритета подвыражения, что и было сделано в приведенном выше примере.
Скобки работают точно так же, как и в математике.
Комбинированные операции присваивания
Помимо простых операций присваивания существует набор комбинированных
операций присваивания. Каждая из них представляет собой сокращенную форму за-
писи какой-то другой операции с переменной и присвоения результата этой пере-
менной. Например:
$а += 5;
эквивалентно:
$а = $а + 5;
Комбинированные операции присваивания существуют для каждой арифметиче-
ской операции, а также для операции конкатенации строк.
Список всех объединенных операций присваивания вместе с результатами их дей-
ствия приведен в табл. 1.2.
Таблица 1.2. Комбинированные операции присваивания РНР
Операция Использование Эквивалентная операция
+= $а += $Ь $а = $а + $Ъ
- = $а -= $Ь $а = $а - $Ь
* “ Sa *= $b $а = $а * $Ь
/= $а /= $Ь $а - $а / $Ь
%= $а %= $Ъ $а = $а % $Ь
.= $а .= $Ь $а = $а . $Ь
Глава 1. Введение в РНР
57
Префиксный и суффиксный инкремент и декремент
Операции префиксного и суффиксного инкремента (++) и декремента (--) анало-
гичны операциям += и -=, но с несколькими отличиями.
Все операции инкремента оказывают двойное действие — они увеличивают значе-
ние переменной на единицу и присваивают переменной это новое значение. Рас-
смотрим следующий код:
$а = 4;
echo ++$а;
Во второй строке используется операция префиксного инкремента, получившая это
название по той причине, что символы ++ предшес твуют переменной $а. В результате
сначала значение $а увеличивается на 1, после чего оператор echo возвращает новое
значение. В рассматриваемом примере значение $а увеличивается и на экран выводит-
ся число 5. Значением всего этого выражения будет 5. (Обратите внимание, что факти-
ческое значение, хранящееся в переменной $а, изменится: результат выполненных
действий не ограничивается простым возвратом значения выражения $а -г 1.)
В то же время, если символы ++ следуют за переменной $а. значит, используется
операция суффиксного инкремента. Она дает другой результат. Рассмотрим такие
строки:
$а = 4;
echo $а++;
В данной ситуации действия выполняются в обратном порядке. То есть, сначала
значение $а возвращается и выводится на экран, и только после этого оно увеличива-
ется на 1. Результатом выполнения этих двух строк будет 4. Именно это значение и
будет выведено на экран. В то же время, после выполнения этого оператора пере-
менная $а принимает значение 5.
Нетрудно догадаться, что операция - - действует аналогично, только в этом случае
значение $а уменьшается, а не увеличивается на 1.
Ссылки
Операция ссылки, обозначаемая как & (амперсанд), может использоваться в соче-
тании с операцией присваивания. Обычно, когда значение одной переменной при-
сваивается другой переменной, создается копия первой переменной, которая сохра-
няется где-то в памяти. Например:
$а = 5;
$Ь = $а;
Приведенные строки кода создают вторую копию значения переменной $а и со-
храняют ее в переменной $Ь. Если впоследствии значение $а подвергнется измене-
нию, значение $Ь останется прежним:
$а = 7; // Значение $Ь по-прежнему остается равным 5
Создания копии можно избежать, используя операцию ссылки &. например:
$а = 5;
$Ь — &$а;
$а = 7; // Теперь оба значения $а и $Ь равны 7
58
Часть I. Использование РНР
Ссылки считаются довольно-таки трудными для понимания. Помните, что ссылка
скорее подобна псевдониму, нежели указателю. И $а, и $Ь указывают на один и тот же
участок памяти. Это можно изменить, сбросив одну из переменных, например:
unset($а);
Сброс не изменяет значения переменной $Ъ (равное 7), но разрывает связь между
переменной $а и значением 7, хранящимся в памяти.
Операции сравнения
Операции сравнения выполняют сравнение двух значений. Выражения, в кото-
рых присутствуют эти операции, возвращают в зависимости от результата сравнения
логические значения true (истина) или false (ложь).
Операция равенства
Операция равенства == (два знака равно) позволяет проверить равенство двух
значений. Например, мы можем воспользоваться выражением
$а == $Ь
для проверки равенства значений, хранящихся в переменных $а и $Ъ. Результатом
этого выражения будет true, если они равны, или false, если они не равны.
Эту операцию легко спутать с операцией присваивания. Это не приведет к выводу
сообщения об ошибке, но в общем случае не даст результата, на который вы, возмож-
но, рассчитывали. В общем случае любые ненулевые значения интерпретируются как
true, а нулевые — как false. Предположим, что две переменных были инициализи-
рованы следующим образом:
$а = 5;
$Ь = 7;
Если затем проверить результат операции $а = $Ь, получится значение true. По-
чему? Значением выражения $а = $Ъ будет значение, присвоенное левому операнду,
которое в данном случае равно 7. Это ненулевое значение, поэтому выражение вы-
числяется как true. Если же вашей целью была проверка выражения $а == $Ъ, ре-
зультат которого равен false, значит, в коде допущена логическая ошибка, которую
исключительно трудно обнаружить. Всегда следует проверять правильность исполь-
зования этих двух операций, дабы убедиться, что вы их не перепутали и выбрали
именно ту, которая нужна.
Подобного рода ошибку очень легко допустить, и. вероятно, вы будете их совер-
шать многократно в своей программистской деятельности.
Другие операции сравнения
РНР поддерживает также ряд других операций сравнения, которые перечислены
в табл. 1.3.
Обратите внимание на операцию проверки идентичности, которая возвращает
значение true только в том случае, если оба операнда равны и имеют один и тот же
тип. Например, 0=='0' даст true, тогда как 0===' 0 ' — false, поскольку первый О
представляет собой целое число, а второй — строку.
Глава 1. Введение в РНР
59
Таблица 1.3. Операции сравнения РНР
Операция Название Использование
== равно $а == $Ь
идентично $а === $Ь
! = не равно $а != $Ь
[ -- не идентично $а != $Ь
<> не равно $а о $Ь
< меньше $а < $Ь
> больше $а > $Ь
<= меньше или равно $а <= $Ь
>= больше или равно $а >= $Ь
Логические операции
Логические операции служат для комбинирования результатов логических усло-
вий. Например, нас может интересовать случай, когда значение переменной $а нахо-
дится в диапазоне между 0 и 100. В этом случае потребуется проверить условия 5а >= 0
и $а <= 100, используя операцию логического И (AND), как продемонстрировано в
следующем примере:
$а >— 0 && $а <—100
РНР поддерживает логические операции AND (И), OR (ИЛИ), XOR (исключающее
ИЛИ) и NOT (НЕ).
Перечень логических операций вместе с описанием их применения представлен в
табл. 1.4.
Таблица 1.4. Логические операции РНР
Операция Название Использование Результат
1 && НЕ И !$Ь $а && $Ь Возвращается true, если значение $а равно false, и наоборот. Возвращается true, если обе переменные $а и
II ИЛИ ?а | | $Ь $Ь имеют значения true; в противном случае возвращается false. Возвращается true, если любая из перемен-
and И $а and $Ь ных $а или $Ь или обе имеют значение true; иначе возвращается false. Та же, что и &&, но с меньшим приоритетом.
or ИЛИ $а or $b Та же, что и | |, но с меньшим приоритетом.
Операции and и or обладают меньшим приоритетом, нежели операции && и | |.
Приоритеты операций более подробно рассматриваются далее в главе.
60
Часть I. Использование РНР
Поразрядные операции
Поразрядные операции позволяют обрабатывать целые числа как последователь-
ности представляющих их разрядов. Вероятно, использовать эти операции вам при-
дется не особенно часто, тем не менее, с их перечнем можно ознакомиться в табл. 1.5.
Таблица 1.5. Поразрядные операции РНР
Операция Название Использование Результат
& поразрядное И $6. & Sb Разряды переменных $а и $Ъ, установ- ленные в единичные состояния, уста- навливаются в единичные состояния в результате.
I поразрядное ИЛИ $а | $b Разряды переменных $а или $Ь, уста- новленные в единичные состояния, устанавливаются в единичные состоя- ния в результате.
поразрядное НЕ Разряды переменной $а. установлен- ные в единичные состояния, устанав- ливаются в нулевые состояния в ре- зультате, и наоборот.
А поразрядное исключающее ИЛИ Sa $Ь Разряды, установленные в единичные состояния в $а или $Ъ, но не в обеих переменных, устанавливаются в еди- ничные состояния в результате.
<< сдвиг влево $а « : $Ь Разряды в переменной $а сдвигаются влево на $Ь позиций.
» сдвиг вправо $а >: > $ь Разряды в переменной $а сдвигаются вправо на $Ь позиций.
Другие операции
В дополнение к рассмотренным выше операциям существует также множество
других.
Операция запятой (,) используется для разделения аргументов функций и элемен-
тов других списков. Обычно она применяется по мере необходимости.
Две специальных операции new и -> используются, соответственно, для создания
экземпляра класса и для доступа к элементам класса. Более подробно эти операции
рассматриваются в главе 6.
Существуют еще три операции, которые крат ко рассматриваются в этой главе.
Тернарная операция
Тернарная операция, а именно. ?:, работает точно так же, как аналогичная опе-
рация в языке С. Она записывается в следующей форме:
условие ? значение, если условие истинно : значение, если условие ложно
Эта тернарная операция подобна рассматриваемой далее в главе версии операто-
ра if-else, записываемой в виде выражения.
Глава 1. Введение в РНР
61
Ниже приведен простой пример:
($grade > 50 ? 'Сдан' : 'Не сдан');
Это выражение содержательно интерпретирует оценку ($grade), полученную сту-
дентом на экзамене, как ' Сдан ' или ' Не сдан '.
Операция подавления ошибки
Операция подавления ошибки @ может использоваться перед любым выражением,
то есть, перед любой конструкцией, которая генерирует или имеет значение.
Например:
$а = @(57/0);
Без операции @ эта строка будет генерировать предупреждение о делении на ноль.
При наличии операции @ вывод сообщения об ошибке подавляется.
В случае такого подавления сообщений об ошибках потребуется предусмотреть
некоторый код для проверки, что именно стало причиной появления того или иного
предупреждения. Если при установке модуля РНР функция track_errors была акти-
визирована, то сообщение об ошибке будет сохраняться в глобальной переменой
$php_errormsg.
Операция выполнения
В действительности операция выполнения представляет собой пару операций —
пару обратных одинарных кавычек (' ')• Их не следует путать с обычными одинар-
ными кавычками — обычно они вводятся с помощью клавиши, на которой располо-
жен символ ~ (тильда).
Все, что заключено в обратные одинарные кавычки, РНР пытается запустить как
команду, вводимую в командной строке сервера. Вывод команды будет значением вы-
ражения.
Например, в среде UNIX-подобных операционных систем можно использовать
следующие строки:
$out = 'Is -la';
echo ’<pre>'.$out.'</pre>';
Ha Windows-сервере этим строкам эквивалентны такие строки:
$OUt = 'dir с:';
echo ’<pre>',$out.'</pre>';
Любая из версий этого кода получит листинг каталога и сохранит его в перемен-
ной $out. Затем его можно вывести в окне браузера либо манипулировать им по сво-
ему усмотрению.
Существуют и другие способы выполнения команд на сервере. Эти вопросы будут
рассматриваться в главе 16.
Операции для работы с массивами
В РНР существует несколько операций для работы с массивами. Операции доступа
к элементам массива ([ ]) позволяют работать с элементами массивов. Кроме того, в
некоторых контекстах массивов можно использовать операцию =>. Все эти операции
рассматриваются в главе 3.
62
Часть I. Использование РНР
Имеется также другие операции для работы с массивами. Несмотря на то что все
они будут подробно описаны в главе 3, все же имеет смысл ознакомиться с их списком
и здесь (табл. 1.6).
Таблица 1.6. Операции для работы с массивами РНР
Операция Название Использование Результат
+ объединение $а & $Ь Возвращает массив, содержащий все, что хранится в переменных $а и $Ь.
— равно $а $ь Возвращает true, если $а и $Ь содержат одни и те же элементы.
— идентично -$а Возвращает true, если $а и $Ь содержат одни и те же элементы, расположенные в одном и том же порядке.
! - не равно $а $ь Возвращает true, если $а и $Ь не равны.
<> не равно $а « : $Ь Возвращает true, если $а и $Ь не равны.
! == не идентично $а >' > $ь Возвращает true, если $а и $Ь не идентичны.
Следует отметить, что все перечисленные в табл. 1.6 операции имеют аналоги для
работы со скалярными переменными и значениями. До тех пор, пока вы помните,
что операция + выполняет сложение скалярных типов данных и объединение масси-
вов (даже если вы не интересуетесь арифметикой множеств, которая лежит в основе
объединения), поведение операции не должно вызывать вопросов. Сравнивать мас-
сивы с данными скалярных типов нельзя.
Операция определения типа
Существует единственная операция определения типа — instanceof. Хотя эта
операция используется в объектно-ориентированном программировании, мы даем ее
здесь ради завершенности рассмотрения. (Концепции объектно-ориентированного
программирования рассматриваются в главе 6.)
Операция instanceof позволяет проверить, является ли заданный объект экземп-
ляром конкретного класса, например:
class sampleclass{};
$myObject = new sampleclass();
if ($myObject instanceof sampleclass)
echo "myObject является экземпляром sampleclass";
Использование операций: вычисление
итоговых сумм для формы
Теперь, когда вы знаете, как пользоваться операциями РНР, можно вычислить
итоговую сумм}' и сумму налога для формы заказа компании Боба.
Для этого в нижнюю часть разрабатываемого PHP-сценария необходимо добавить
следующий код:
$totalqty = 0;
$totalqty = $tireqty + $oilqty + $sparkqty;
echo 'Заказано товаров: '.Stotalqty.'<br />';
Глава 1. Введение в PHP
63
Stotalamount = 0.00;
define('TIREPRICE’,100);
define('OLDPRICE',10) ;
define(•SPAKPRICE', 4);
Stotalamount = Stireqty * TIREPRICE
+ $oilqty * OILPRICE
+ $sparkqty * SPARKPRICE;
echo 'Итого: $’.number_format(Stotalamount,2).'<br />';
Staxrate = 0.10; // местный налог с продаж составляет 10%
Stotalamount = Stotalamount * (1 + Staxrate);
echo 'Всего, включая налог с продаж: $'.Stotalamount.'<br />';
После обновления страницы в окне браузера вы увидите что-то, похожее на пока-
занное на рис. 1.5.
Рис. 1.5. Итоговые суммы заказа клиента вычислены, сформа-
тированы и отображены
Как видите, в этом фрагменте кода используется несколько операций. Операции
сложения (+) и умножения (*) применяются для вычисления итоговых значений, а
операция конкатенации строк (.) — для подготовки вывода в окне брач зера.
Кроме того, с помощью функции number_format । । итоговые суммы были
сформатированы и представлены в виде строк с двумя десятичными разрядами. Упо-
мянутая функция входит в состав PHP-библиотеки математических функций.
Если вы внимательно проанализируете вычисления, ч вас. возможно, возникнет
вопрос, почему для них был выбран именно такой их порядок. Например, рассмот-
рим следующий оператор:
Stotalamount = $tireqty * TIREPRICE
+ Soilqty * OILPRICE
+ Ssparkqty * SPARKPRICE;
64
Часть I. Использование PHP
Итог кажется правильным, но почему умножение выполнилось перед сложением?
Это обусловлено приоритетом операций, то есть порядком их выполнения.
Приоритет и ассоциативность:
вычисление выражений
В общем случае операции обладают приоритетами, или порядком их вычисления.
Кроме того, одной из характеристик операции является ее ассоциативность, оп-
ределяющая порядок выполнения операций с одинаковыми приоритетами. В общем
случае операции могут выполняться слева направо, справа налево, либо же порядок
их выполнения не имеет значения.
Приоритеты и ассоциативность операций в РНР представлены в табл. 1.7.
В верхней части этой таблицы указаны операции с наименьшим приоритетом, по
мере продвижения сверху вниз приоритеты операций возрастают.
Таблица 1.7. Приоритеты операций в РНР
Ассоциативность Операции
слева направо
слева направо or
слева направо хог
слева направо and
справа налево print
слева направо = -= *= /= .= %= &= |= -= ~= «= »=
слева направо ? ;
слева направо ! |
слева направо &&
слева направо
слева направо
слева направо &
неприменима == J= =-~ 1= ~
неприменима <<=>>=
слева направо « »
слева направо + — .
слева направо * / %
справа налево ! ~ ++ — (int) (float) (string) (array) (object) @
справа налево [J
неприменима new
неприменима 0
Обратите внимание, что наивысшим приоритетом обладает операция, которую
мы еще не рассматривали: хорошо знакомые круглые скобки. Они повышают при-
оритеты любых заключенных в них операций. Именно с их помощью при необхо-
димости можно изменять порядок выполнения операций, определенный их при-
оритетами.
Глава 1. Введение в РНР
65
Вспомним фрагмент из последнего примера:
$totalamount = $totalamount * (1 + $taxrate);
Если записать следующим образом:
$totalamount = $totalamount * 1 + $taxrate;
то операция умножения, имеющая более высокий приоритет по сравнению с опера-
цией сложения, выполнялась бы первой, что привело бы к неверному результату.
С помощью круглых скобок можно добиться, чтобы вначале вычислялось подвыра-
жение 1 + $taxrate.
В выражении можно использовать любой набор пар круглых скобок. При этом
первым будет вычисляться выражение, заключенное в самые внутренние скобки.
Также следует отметить еще одну операцию, присутствующую в табл. 1.7, которая
еще не рассматривалась: языковую конструкцию print, представляющую собой экви-
валент echo. Обе конструкции генерируют вывод в окно браузера.
В основном в книге используется echo, тем не менее, вы можете применять print,
если эта конструкция кажется вам более читабельной. Реально ни print, ни echo не
являются функциями, однако обе они вызываются подобно функциям, с передачей
им параметров в круглых скобках. Обе конструкции можно также рассматривать как
операции — вы просто помещаете необходимую строку после ключевого слова print
или echo.
Вызов print как функции приводит к возврату ею значения (1). Данная возмож-
ность может оказаться полезной, если необходимо генерировать вывод внутри более
сложного выражения, тем не менее, учитывайте, что print существенно медленнее
echo.
Использование функций для работы
с переменными
Прежде чем покинуть мир переменных и операций, рассмотрим PHP-функции для
работы с переменными. В РНР доступна библиотека функций, с помощью которых
можно манипулировать и проверять значения переменных различными способами.
Проверка и установка типов переменных
Большая часть функций для работы с переменными связана с проверкой типов.
Двумя самыми общими функциями являются gettype() и settype , Они имеют
показанные ниже прототипы, которые определяют, какие аргументы и.м следует пе-
редавать, а также какие значения они возвращают.
string gettype(mixed var);
int settype(string var, string type);
При вызове функции gettype () ей передается некоторая переменная. Функция
(“get type” означает “получить тип”) определяет тип этой переменой и возвращает
строку, содержащую имя типа или фразу "unknown type" (“тип неизвестен”), если
тип переменной не принадлежит к числу стандартных типов, то есть, не является
boolean, integer, double(float), string, array, object, resource или NULL.
66
Часть I. Использование PHP
При вызове функции settype() ей необходимо передать переменную, тип кото-
рой требуется изменить, и строку, содержащую новый тип переменной из приведен-
ного выше списка.
Примечание
В этой книге и в документации, доступной на сайте php.net, вы можете встретить ссылки на
так называемый смешанный (mixed) тип данных. Такого типа данных не существует, однако
поскольку РНР столь гибок в отношении поддержки типов, многие функции могут принимать в
качестве аргументов данные сразу нескольких типов. Аргументы, которые допускают передачу
данных нескольких типов, указываются как имеющие псевдотип mixed.
Данные функции (“set type” означает “установить тип”) используются, как показа-
но в следующем примере:
$а = 56;
echo gettype($а).'<br />’;
settype ($а, 'float');
echo gettype($a).'<br />';
Перед первым вызовом функции gettype () переменная $а имеет тип integer.
После вызова функции settype () ее тип изменяется на float.
РНР предоставляет также ряд функций для проверки типов. Каждая из этих функ-
ций принимает переменную в качестве аргумента и возвращает значение true или
false. К упомянутым функциям относятся:
is_array()
is._double (), is_f loat (), is_real () (все они — одна и та же функция)
i s_long (), is_int (), i s_integer () (все они — одна и та же функция)
is_string()
is_object()
is_resource()
is_null()
is_scalar() (проверяет, является ли переменная скалярной, то есть имеет ли
она тип integer, boolean, string или float)
is_numeric() (проверяет, является ли переменная числом или какой-нибудь
числовой строкой)
is_callable () (проверяет, является ли переменная именем допустимой функции).
Проверка состояния переменных
В РНР имеется несколько функций, предназначенных для проверки состояния
переменных.
Первая из них, isset (), имеет следующий прототип:
boolean isset(mixed var);
Эта функция (“is set” — “установлена”) принимает в качестве аргумента имя пере-
менной и возвращает значение true, если переменная существует, и false в против-
ном случае. Вы также можете передать ей разделенный запятыми список перемен-
ных, и функция isset () возвратит true, если все переменные в списке установлены.
Глава 1. Введение в РНР
67
Переменную можно удалить, используя родственную функцию unset < >. которая
имеет показанный ниже прототип:
int unset(mixed var);
Эта функция (“unset” — “не установлена”) фактически удаляет переменную. пере-
данную ей в качестве аргумента.
И, наконец, в РНР доступна функция empty (). Она проверяет существование пере-
менной и наличие у нее непустого, ненулевого значения, возвращая, соответственно,
значение true или false. Эта функция (“empty” — “пустая”) имеет следующий прототип:
int empty(mixed var);
Рассмотрим пример использования трех перечисленных выше функций.
Посмотрите, как работают эти функции, для чего временно добавьте в разрабаты-
ваемый сценарий следующие строки кода"
echo 'isset($tireqty):
echo 'isset($nothere):
echo 'empty(Stireqty):
echo 'empty($nothere):
.isset(Stireqty).'<br />’;
.isset($nothere).'<br />';
.empty(Stireqty).'<br />';
.empty(Snothere).'<br />';
Обновите страницу, чтобы увидеть результат.
Функция isset () должна возвратить для переменной Stireqty значение 1 (true)
вне зависимости от того, какое значение введено в поле формы, и от того, введено ли
вообще значение. Возвращаемое значение функции empty () зависит от введенного
значения.
Переменная $nothere не существует, поэтому для нее функция isset () возвраща-
ет пустое значение (false), в то время как функция empty () выдает значение true.
Эти функции могут оказаться полезными, если требуется проверить, заполнил ли
пользователь соответствующие поля формы.
Повторная интерпретация переменных
Эффекта, эквивалентного приведению типа переменной, можно достичь и с по-
мощью функции. Для этих целей используются три функции:
int intval(mixed var);
float floatval(mixed var);
string strval(mixed var);
Каждая из них принимает в качестве аргумента переменную и возвращает значе-
ние переменной, преобразованное к соответствующему типу.
Функция intval () также позволяет указать основание системы счисления, когда
переменная преобразуется из строкового представления. (В результате можно пре-
образовывать, например, шестнадцатеричные строки в целочисленные значения.)
Реализация управляющих структур
Управляющие структуры — это языковые конструкции, которые позволяют управ-
лять ходом выполнения программы или сценария. Их можно разделить на две кате-
гории: на условные структуры (или структуры ветвления) и структуры повторения
68
Часть I. Использование РНР
(или циклы). В следующем разделе мы рассмотрим специфические реализации каж-
дого вида структур в РНР.
Принятие решений на основе условий
Если необходимо, чтобы сценарий разумно реагировал на информацию, введен-
ную пользователем, код должен быть способным принимать решения. Конструкции,
которые указывают программе на необходимость принимать решение, называются
условными операторами.
Операторы if
Для принятия решений используется оператор if. Ему необходимо задать условие.
Если условие имеет значение true, то выполняется следующий за ним блок кода. Ус-
ловие в операторе i f должно быть заключено в круглые скобки ().
Например, если заказ посетителя в компании “Автозапчасти от Боба” не включает
ни покрышек, ни бутылок с маслом, ни свечей зажигания, вероятно, это связано со
случайным нажатием кнопки Отправить заказ. Вместо сообщения “Заказ обработан”
страница могла бы выдать более приличествующее ситуации сообщение.
Если посетитель вообще не заказывает запчастей, вероятно, имеет смысл вывести
сообщение наподобие “Вы ничего не заказали на предыдущей странице!”. Это легко
сделать с помощью следующего оператора if:
if( $totalqty == 0 )
echo 'Вы ничего не заказали на предыдущей странице!<Ьг />';
В этом операторе используется условие $totalqty == 0. Помните, что операция
равенства (==) ведет себя иначе, нежели операция присваивания (=).
Условие $totalqty == 0 будет иметь значение true, если значение переменной
$totalqty равно нулю. Если значение переменной $totalqty не равно нулю, значе-
ние условия будет равно false. Когда значением условия будет true, оператор echo
выполнится.
Блоки кода
Часто внутри такого условного оператора, как if, требуется выполнить более од-
ного оператора. В этом случае соответствующая последовательность операторов за-
писывается в виде блока. Чтобы объявить блок, операторы потребуется заключить в
фигурные скобки:
if( $totalqty == 0 )
{
echo '<font color=red>';
echo 'Вы ничего не заказали на предыдущей странице!<Ьг />';
echo ’</font>'; »
}
Три строки кода, заключенные в фигурные скобки, теперь представляют собой
блок. Когда значением условия является true, выполняются все три строки. Если
значением условия будет false, все три строки кода будут проигнорированы.
Глава 1. Введение в РНР
69
Примечание
Как отмечалось выше, выравнивание кода в РНР значения не имеет. Тем не менее, вы можете
использовать отступы только для повышения удобства чтения кода. Отступы применяются для
того, чтобы с первого взгляда было видно, какие строки будут выполняться и при каких усло-
виях, какие операторы сгруппированы в блоки и какие операторы являются частью циклов или
функций. В приведенных ранее примерах операторы, выполнение которых зависит от опера-
тора if, и операторы, образующие блок, были записаны с отступами.
Операторы else
Часто не только приходится принимать решение, должно или не должно выпол-
няться то или иное действие, но и выбирать это действие из некоторого набора воз-
можных действий.
Оператор else позволяет определить альтернативное действие, которое должно
выполняться, если значение условия в операторе if окажется равным false. Скажем,
в рассматриваемом примере необходимо предупредить клиента о том, что он ничего
не заказал. С другой стороны, если он сделал заказ, вместо предупреждения ему дол-
жен выводиться список заказанных товаров.
Если немного изменить код и добавить в него оператор else, можно отображать
либо уведомление, либо итоговую информацию.
if( Stotalqty == 0 )
{
echo 'Вы ничего не заказали на предыдущей странице!<Ьг />';
}
else
{
echo Stireqty.' автопокрышексЬг />';
echo Soilqty.' бутылок машинного масласЬг />';
echo $sparkqty.' свечей зажиганижЬг />';
}
Вкладывая операторы if один в другой, можно строить более сложные логиче-
ские цепочки. Приведенный ниже код не только обеспечивает отображение итого-
вой информации, когда значение условия Stotalqty == 0 равно true, но и отобра-
жает каждую из итоговых строк при выполнении ее собственного условия.
iff Stotalqty == 0)
{
echo 'Вы ничего не заказали на предыдущей странице!<br ,>';
}
else
{
if ( $tireqty>0 )
echo Stireqty.’ автопокрышексЬг />';
if ( $oilqty>0 )
echo $oilqty.' бутылок машинного масласЬг />';
if ( $sparkqty>0 )
echo $ sparkqty. ' свечей зажиганижЬг />' ;
}
70
Часть I. Использование PHP
Операторы elseif
Во многих случаях принятие решения предполагает выбор соответствующего ва-
рианта из некоторого множества возможных вариантов. Последовательность этого
множества вариантов можно создать с помощью оператора elseif, который пред-
ставляет собой комбинацию операторов else и if. При наличии последовательности
условий программа может проверять каждое из них до тех пор, пока не отыщет то,
значением которого является true.
Боб предоставляет скидки при заказе большого количества автопокрышек. Схема
скидок выглядит следующим образом:
Приобретение менее 10 автопокрышек — без скидки.
Приобретение от 10 до 49 автопокрышек — скидка 5%.
Приобретение от 50 до 99 автопокрышек — скидка 10%.
Приобретение 100 и более автопокрышек — скидка 15%.
Можно подготовить программный код, вычисляющий скидки, с использованием
условий и операторов if и elseif. Для объединения двух условий в одно применяет-
ся операция И (&&).
if! $tireqty < 10 )
$discount = 0;
elseif! Stireqty >= 10 && $tireqty <= 49 )
$discount = 5;
elseif( $tireqty >= 50 && $tireqty <= 99 )
$discount = 10;
elseif! $tireqty > 100 )
$discount = 15;
Обратите внимание на то, что можно применять как elseif, так и else if —
с пробелом или без оного: оба варианта допустимы.
При использовании каскадных наборов операторов elseif следует помнить, что
выполняется только один из блоков или операторов. В рассматриваемом примере это
не существенно, ибо все условия являются взаимоисключающими, поскольку в каж-
дый конкретный момент времени только одно из них может принимать значение
true. Если условия записаны так, что в один и тот же момент времени значение true
принимают сразу несколько условий, то выполняется только тот блок или оператор,
который следует за первым истинным условием.
Операторы switch
Оператор switch работает аналогично оператору if, но позволяет условном}7 вы-
ражению иметь в качестве результата более двух значений. В операторе if условие
принимает значение true или false. В операторе switch условие может принимать
любое число различных значений в тех случаях, когда результат его вычисления
принимает простой тип (integer, string или float). Чтобы иметь возможность реа-
гировать на каждое такое значение, вы должны предусмотреть для него соответст-
вующий оператор case, а также (не обязательно) определить действия, выполняемые
по умолчанию, когда возникает случай, не предусмотренный конкретным операто-
ром case.
Глава 1. Введение в РНР
71
Боб желает знать, какие формы рекламы содействуют успеху его предприятия.
Для этого в форму заказа можно добавить вопрос.
Добавьте в форму приведенный ниже HTML-код, после чего она должна принять
вид, показанный на рис. 1.6.
<tr>
<td>KaK вы нашли компанию "Автозапчасти от Боба? "</td>
<tdxselect name=” find">
<option value = "а">Я регулярный клиент</орб1оп>
<option value = "b">B телевизионной рекламе</орб!оп>
<option value = "c">B телефонном справочнике</орб1оп>
<option value = "<3">Кто-то порекомендовал</орД1оп>
</select>
</td>
</tr>
Рис. 1.6. Теперь форма заказа задает посетителям вопрос, по
чьей рекомендации они обратились за товаром в компанию “Ав-
тозапчасти от Боба”
Показанный выше HTML-код добавил новую переменную формы, значением ко-
торой будет ' а', ' b ', ' с ' или ' d'. Эту новую переменную можно было бы обработать
с помощью последовательности операторов if Helseif:
if($find == 'a')
echo '<р>Регулярный клиент.</p>';
elseif($find == 'b')
echo '<р>Клиент, обратившийся после телевизионной рекламы.</р>';
elseif($find == 'с')
echo '<р>Клиент, обратившийся в результате нахождения информации в теле-
фонном справочнике.</р>';
elseif($find == 'd')
echo '<р>Клиент, обратившийся в результате чьей-то устной
рекомендации.</р>1;
72
Часть I. Использование РНР
В качестве альтернативы можно воспользоваться оператором switch:
switch($find)
{
case 'a' :
echo '<р>Регулярный клиент.</p>';
break;
case 'b' :
echo '<р>Клиент, обратившийся после телевизионной рекламы.</р>';
break;
case 'с' :
echo ’<р>Клиент, обратившийся в результате нахождения информации в те-
лефонном справочнике.</р>';
break;
case 'd1 :
echo '<р>Клиент, обратившийся в результате чьей-то устной рекоменда-
ции. </р>' ;
break;
default :
echo 1<р>Мы не можем определить, на основе какой информации вы нас на-
шли. </р>';
break;
}
(В последних двух примерах предполагается, что вы уже получили переменную
$f ind из массива $_POST.)
Оператор switch ведет себя несколько иначе, нежели оператор if или elseif.
Оператор if выбирает на выполнение только один оператор, если специально не
используются фигурные скобки для создания блока операторов. Оператор switch
действует по-другому принципу. Когда оператор case в рамках оператора switch ак-
тивизируется, РНР выполняет следующие за ним операторы, один за другим, до тех
пор, пока не столкнется с оператором break. Без него оператор switch выполнял бы
весь код, следующий за оператором case, условие которого равно true. Но достиже-
нии оператора break выполняется строка кода, следующая за оператором switch.
Сравнение различных условных операторов
Если вы не работали с операторами, описанными в предыдущих разделах, у вас
вполне может возникнуть вопрос: “Какой же из них самый лучший?”
На упомянутый вопрос нельзя дать однозначный ответ. Не существует ничего из
того, что можно сделать с помощью одного или нескольких операторов else, elseif
или switch, но чего нельзя было бы сделать с помощью определенного набора опера-
торов if. В каждой конкретной ситуации используйте те условные операторы, кото-
рые обеспечивают удобочитаемость и легкое восприятие программного кода. По ме-
ре накопления практического опыта программирования вы выработаете для себя
соответствующие критерии.
Глава 1. Введение в РНР
73
Повторение действий с помощью итераций
Одна из задач, с которыми компьютеры всегда справлялись исключительно ус-
пешно — это автоматизация повторяющихся действий. Если нужно многократно вы-
полнить одну и ту же последовательность действий, вы можете воспользоваться цик-
лом, чтобы повторить определенные фрагменты программы.
Бобу необходима таблица, отображающая стоимость доставки, которая добавля-
ется к стоимости заказа клиента. В условиях, когда для доставки партии товара Боб
использует курьера, стоимость доставки зависит от расстояния и может быть вычис-
лена с помощью простой формулы.
Таблица стоимости доставки может выглядеть так, как показано на рис. 1.7.
HTML-код, выводящий эту таблицу, представлен в листинге 1.3. Несложно убе-
диться, что многие фрагменты этого достаточно длинного кода многократно повто-
ряются.
Листинг 1.2. freight.html —• HTML-код для таблицы стоимости доставки в компании
“Автозапчасти от Боба”
<html>
<body>
<table border = "0" cellpadding = "3">
<tr>
<td bgcolor = "#CCCCCC" align = "center">PaccTOHHwe</td>
<td bgcolor = "#CCCCCC" align = "center”>CTOMMocTb</td>
</ tr>
<tr>
<td align = "right">50</td>
<td align = ”right">5</td>
</tr>
<tr>
<td align = "right">100</td>
<td align = "right">10</td>
</tr>
<tr>
<td align = "right">150</td>
<td :/tr> tr> align = ,,right">15</td>
<td align = ”right">200</td>
<td /tr> tr> align = "right">20</td>
<td align = "right">250</td>
<td :/tr> align = "right">2 5</td>
</table>
</body>
</html>
74
Часть I. Использование PHP
Вместо того чтобы поручать ввод HTML-кода человеку, котором}' выполнение по-
добной задачи быстро наскучивает и которому, к тому же, необходимо платить за по-
траченное время, лучше и дешевле поручить это дело неутомимому компьютеру.
Операторы цикла указывают РНР о необходимости многократного выполнения
того или иного оператора или блока операторов.
Рис. 1.7. Эта таблица отображает стоимость дос-
тавки товара в зависимости от расстояния
Циклы while
Простейшим видом цикла в РНР является, пожалуй, цикл while. Подобно опера-
тору if, в основе этого оператора лежит проверка условия. Различие между циклом
while и оператором if состоит в том, что если условие принимает значение true,
оператор if выполняет следующий за ним блок кода только один раз. Цикл while вы-
полняет блок операторов многократно, пока условие продолжает быть равным true.
В общем случае цикл while используется, когда не известно, для скольких итера-
ций будет выполняться условие. Если же нужно выполнить фиксированное число
итераций, стоит подумать об использовании цикла for.
Базовая структура цикла while имеет следующий вид:
while( условие ) выражение;
Показанный ниже цикл while выводит на экран числа от 1 до 5.
$r.um = 1 ;
while ($num <= 5 )
{
echo $r.um. "<br />";
$num++;
}
Условие проверяется в начале каждой итерации. Если оно принимает значение
false, блок операторов выполняться не будет и цикл завершается. После этого вы-
полняется оператор, следующий за циклом.
Цикл while можно использовать для выполнения чего-то более полезного, напри-
мер, для отображения повторяющейся таблицы стоимости доставки, которая была
показана на рис. 1.7.
Глава 1. Введение в РНР
75
В листинге 1.3 цикл while используется для построения таблицы стоимости дос-
тавки.
Листинг 1.3. freight.php — Генерация таблицы стоимости доставки компании
“Автозапчасти от Боба” с помощью РНР
<body>
<table border = "О” cellpadding = "3”>
<tr>
<td bgcolor = "ttCCCCCC" align = "center">PaccTOflHMe</td>
<td bgcolor = ”#CCCCCC" align = "center">CTOMMOOTb</td>
</tr>
<7
$distance = 50;
while ($distance <= 250 )
{
echo "<tr>\n <td align = 'right'>$distance</td>\n";
echo " <td align = 1 right'>". $distance I 10 ."</td>\n</tr>\n";
$distance += 50;
}
?>
</table>
</body>
</html>
Чтобы сгенерированный сценарием HTML-код стал удобочитаемым, следует
включить в него символы новой строки и пробелы. Как было показано выше, браузе-
ры на это не реагируют, но для людей, читающих распечатку, все это имеет значение.
Очень часто приходится пользоваться просмотром страницы в виде HTML, когда
получается не тот вывод, который ожидался.
В листинге 1.3. внутри некоторых строк встречается последовательность симво-
лов /п. Если она находится внутри строки, заключенной в двойные кавычки, то эта
конструкция представляет собой символ новой строки.
Циклы for и foreach
Описанный в предыдущем разделе способ использования циклов while является
достаточно общеупотребительным. Сначала устанавливается начальное значение
счетчика. Перед каждой итерацией значение счетчика проверяется внутри условия. В
конце каждой итерации значение счетчика изменяется.
Цикл подобного типа можно записать и в более компактной форме с использова-
нием оператора for.
Базовая структура цикла for имеет следующий вид:
for( выражение!; условие; выражение2}
выражение!;
Выражение! выполняется один раз в начале цикла. Обычно в нем устанавливается
начальное значение счетчика.
76
Часть I. Использование РНР
Выражение условие проверяется перед каждой итерацией. Если это выражение
возвращает значение false, цикл останавливается. Обычно в этом выражении вы-
полняется сравнение значения счетчика с предельным значением.
Выражение2 выполняется в конце каждой итерации. Обычно в нем изменяется
значение счетчика.
ВыражениеЗ выполняется один раз во время каждой итерации. Обычно это выра-
жение представляет собой блок кода и содержит собственно тело цикла.
Пример цикла while, представленный в листинге 1.3, можно переписать с исполь-
зованием цикла for. PHP-код примет следующий вид:
for($distance = 50; $distance <= 250; $distance += 50)
{
echo "<tr>\n <td align = 'right'>$distance</td>\n";
echo " <td align = 'right’>". $distance / 10 ."</td>\n</tr>\n";
}
В функциональном смысле циклы while и for идентичны. Однако цикл for имеет
несколько более компактную форму и содержит на две строки меньше.
Оба цикла эквивалентны — ни один из них ничем не лучше и не хуже другого.
В конкретной ситуации вы можете использовать тот из них, который вам кажется
более подходящим.
Попутно отметим, что можно объединять переменные переменных и циклы for
для организации итераций по последовательности повторяющихся полей формы.
Например, при наличии полей формы с такими именами, как, скажем, namel, name2,
патеЗ и так далее, их можно обрабатывать следующим образом:
for ($i=l; $i <= $numnames; $i++)
{
$temp= “nameSi";
echo $$temp. '<br />'; II здесь может быть любая другая обработка
}
Динамически генерируя имена переменных, можно обращаться к каждому из по-
лей по очереди.
Наряду с циклом for существует цикл foreach, специально предназначенный для
работы с массивами. Все подробности его применения будут рассматриваться в главе 3.
Циклы do. .while
Последний тип цикла, который мы рассмотрим, работает несколько иначе. Общая
структура оператора do. .while имеет следующий вид:
do
выражение;
while) условие );
Цикл do. .while отличается от цикла while тем, что в нем условие проверяется в
конце. Это означает, что в цикле do. .while оператор или блок операторов внутри
цикла всегда выполняется, по крайней мере, один раз.
Глава 1. Введение в РНР
77
Даже в приведенном ниже примере, где условие с самого начала имеет значение
false и никогда не может принять значение true, цикл выполнится один раз до того,
как условие будет проверено и цикл завершится.
$num = 100;
do
{
echo $num.1<br /> ’ ;
}
while ($num < 1 );
Выход из управляющей структуры
или сценария
Если вы хотите остановить выполнение какого-либо фрагмента, можете воспользо-
ваться одним из трех подходов в зависимости от эффекта, который желаете получить.
Если необходимо прекратить выполнение цикла, можно воспользоваться опера-
тором break, как было описано ранее в разделе, посвященном оператору switch.
В случае применения оператора break в цикле выполнение сценария продолжится,
начиная со строки, следующей за циклом.
Если требуется перейти к следующей итерации цикла, можно воспользоваться
оператором continue.
Для завершения выполнения всего PHP-сценария служит оператор exit. Обычно
этот оператор используется при проверке на ошибки. Например, ранее приведенный
пример можно было бы изменить следующим образом:
if( Stotalqty == 0 )
{
echo 'Вы ничего не заказали на предыдущей странице!<br />';
exit;
}
Оператор exit прекращает выполнение оставшейся части РНР-сценария.
Использование альтернативного
синтаксиса управляющих структур
Для всех рассмотренных выше управляющих с труктур предусмотрена альтерна-
тивная форма синтаксиса, при которой открывающая фигурная скобка ({) заменяет-
ся двоеточием (:), а закрывающая фигурная скобка (}) — новым ключевым словом,
коим может быть endif, endswitch, endwhile, endfor или endforeach, в зависимости
от используемой управляющей структуры. Альтернативная форма синтаксиса недос-
тупна для циклов do. .while.
Например, показанный ниже код:
if ( $totalqty == 0)
{
echo 'Вы ничего не заказали на предыдущей странице!<br />';
exit;
}
78
Часть I. Использование РНР
может быть преобразован с использованием ключевых слов if и endif следующим
образом:
if ( Stotalqty == 0):
echo 'Вы ничего не заказали на предыдущей странице!<br />';
exit;
endi f;
Использование declare
Еще одна управляющая структура РНР, declare, используется относительно редко
по сравнению с другими структурами. Общая форма этой управляющей структуры
выглядит следующим образом:
declare (директива)
{
// блок кода
)
Данная структура служит для установки директив выполнения некоторого блока ко-
да, то есть правил, в соответствие с которыми будет запускаться определенный код. В
настоящее время реализована только одна директива выполнения, называемая ticks.
Ее можно установить, указав в качестве аргумента директива конструкцию ticks=n.
Эта директива позволяет выполнять заданную функцию через каждых п строк кода
внутри блока, что является исключительно полезным для целей профилирования и
отладки.
Управляющая структура declare упомянута здесь только ради полноты. Некото-
рые примеры использования функций ticks будут рассмотрены в главах 24 и 25.
Что дальше
Теперь вы уже знаете, как получить заказ клиента и как им манипулировать. В сле-
дующей главе мы рассмотрим вопросы сохранения заказа с тем, чтобы позже его
можно было извлечь и доставить заказанную в нем продукцию.
Глава 1. Введение в РНР
79
2
Сохранение и
восстановление данных
Теперь, когда вы научились получать доступ и манипулировать данными, введен-
ными пользователем в HTML-форму, можно приступать к рассмотрению спосо-
бов сохранения этой информации с целью будущего ее использования. В большинст-
ве случаев, включая и пример, рассмотренный в предыдущей главе, данные необхо-
димо сохранять и позже их загружать для использования. В рассматриваемом случае
нужно записывать формы заказов клиентов в некоторое хранилище, дабы позднее
можно было их обслужить.
В этой главе мы рассмотрим, как созданную в предшествующем примере форму
заказа клиента сначала записать в файл, а затем прочитать ее из файла. Мы рассмот-
рим также, почему это решение не всегда является наилучшим. При наличии большо-
го количества заказов необходимо пользоваться системой управления базами данных,
такой как MySQL.
В этой главе, помимо прочих, рассматриваются следующие темы:
Сохранение данных для дальнейшего их использования.
Открытие файла.
Создание файла и запись в файл.
Закрытие файла.
Считывание из файла.
Блокирование файлов.
Удаление файлов.
Другие полезные файловые функции.
Более рациональный способ обработки: системы управления базами данных.
Сохранение данных для дальнейшего
их использования
Существуют два основных способа хранения данных: в двумерных файлах и в ба-
зах данных.
Двумерный файл может иметь множество форматов, но в общем случае под дву-
мерным, или плоским (flat) файлом мы будем понимать простой текстовый файл. В рас-
сматриваемом ниже примере заказы клиента записываются в текстовый файл, по од-
ному заказу в каждой строке.
Этот способ весьма прост, в то же время он ограничивает использование средств
манипулирования данными, как будет показано далее в этой главе. Если вам прихо-
дится иметь дело с информацией объема, который традиционно встречается в ре-
альных приложениях, вы, скорее всего, отдадите предпочтение какой-то базе дан-
ных. Тем не менее, двумерные файлы находят достаточно широкое применение,
поэтому в некоторых случаях нужно знать, как ими пользоваться.
Запись и чтение из файлов в РНР практически идентичны выполнению этих опе-
раций в языке С. Если вам уже доводилось программировать на языке С или создавать
сценарии оболочки UNIX, этот процесс должен быть вам хорошо знаком.
Сохранение и извлечение заказов
в компании “Автозапчасти от Боба”
В этой главе будет использоваться форма заказа, несколько измененная по срав-
нению с той, что рассматривалась в предыдущей главе. Мы начнем с той формы и с
PHP-кода, созданного для обработки данных заказа.
Примечание
Используемый в этой главе HTML- и PHP-код можно найти в папке chapter02 на прилагаемом
компакт-диске.
Мы изменили форму, включив в нее адрес доставки клиента. Последний вариант
формы можно видеть на рис. 2.1.
Рис. 2.1. В эту версию формы заказа включен адрес доставки клиента
Глава 2. Сохранение и восстановление данных
81
Поле формы, предназначенное для ввода адреса доставки, имеет имя address.
В результате мы получаем переменную, к которой в PHP-коде можно обращаться как
к $_REQUEST [ 1 address ' ], $_POST [1 address' ] либо $_GET [' address 1 ], в зависимости
от метода отправки данных формы. (За дополнительной информацией обращайтесь
в главу 1.)
Все поступающие заказы записывается в один и тот же файл. Затем создается Web-
интерфейс, чтобы служащие компании “Автозапчасти от Боба” смогли просматри-
вать полученные заказы.
Обработка файлов
Чтобы записать некоторые данные в файл, необходимо выполнить следующие три
действия:
1. Открыть файл. Если файл еще не существует, его нужно создать.
2. Записать данные в файл.
3. Закрыть файл.
Аналогично, чтобы прочитать данные из файла, необходимо также выполнить
три действия:
1. Открыть файл. Если файл открыть невозможно (например, он не существу-
ет), эту ситуацию необходимо распознать и предусмотреть корректное ее
разрешение.
2. Прочитать данные из файла.
3. Закрыть файл.
Если вы собираетесь читать данные из файла, то должны выбрать, какую часть
файла следует считывать за один раз. Ниже мы рассмотрим все эти действия более
подробно.
Начнем с рассмотрения процедуры открытия файла.
Открытие файла
Для открытия файла в РНР служит функция fopen(). При этом необходимо ука-
зать, как он будет использоваться. Способ использования носит название режима фай-
ла (file mode).
Режимы файлов
Операционная система, установленная на сервере, должна знать, что вы намере-
ны делать с открываемым файлом. Она должна знать, может ли соответствующий
файл быть открыт другим сценарием в то время, когда он уже открыт вашим сцена-
рием, и обладаете ли вы (либо владелец сценария) правами на подобное его исполь-
зование. По сути, режимы файла предоставляют операционной системе механизм
выбора способа обработки запросов на доступ, поступающих от других пользователей
или сценариев, а также метода проверки, имеете ли вы доступ и права на работу' с
конкретным файлом.
82
Часть I. Использоввнив РНР
Открывая файл, вы должны принять во внимание следующие три момента:
1. Файл можно открыть только для чтения, только для записи или для чтения и
записи.
2. При выполнении записи в файл, возможно, потребуется перезаписывать лю-
бое существующее содержимое файла либо добавлять новые данные в конец
файла. Может также возникать необходимость изящно завершить программу,
не перезаписывая файл, если он существует.
3. При попытке внесения записи в файл в системе, которая проводит различие
между бинарными и текстовыми файлами, возможно, потребуется указать тип
файла.
Функция fopen() поддерживает любые комбинации трех упомянутых вариантов.
Использование функции fopen() для открытия файла
Предположим, что требуется сохранить заказ клиента в файле заказов компании
“Автозапчасти от Боба”. Этот файл можно открыть для записи с помощью следующе-
го оператора:
$fp = fopen("$DOCUMENT-ROOT/..zorders/orders.txt", ' w');
При вызове функции fopen () необходимо передать два или три параметра.
Обычно используются два параметра, как показано в приведенной выше строке кода.
Первым параметром должен быть файл, который должен быть открыт. При этом
можно указать путь к файлу, как было сделано в приведенной выше строке кода —
файл orders . txt находится в каталоге orders. Мы использовали встроенную РНР-
переменную $_SERVER [ ' DOCUMENT—ROOT ' ], но поскольку полные имена переменных
слишком громоздкие, ей было присвоено короткое имя.
Эта переменная указывает на основание дерева документов используемого вами
Web-сервера. Кроме того, с помощью символа . . был обозначен “родительский ката-
лог корневого каталога документов". В целях безопасности этот каталог находится
вне дерева документов. Мы не хотим, чтобы этот файл был доступен в Web, кроме как
только через предоставляемый нами интерфейс. Этот путь называется относитель-
ным, поскольку он описывает позицию в файловой системе относительно корня де-
рева документов.
Как и в случае коротких имен, которые присваиваются переменным формы, в на-
чале сценария должна находиться следующая строка:
$DOCUMENT_ROOT = $_SERVER['DOCUMENT-ROOT'];
которая скопирует содержимое переменной длинного стиля в переменную короткого
стиля.
Подобно тому, как существуют различные способы доступа к данным формы, точно
так же существуют различные способы доступа к зарезервированным серверным пере-
менным. В зависимости от настройки вашего сервера (см. главу 1), вы можете получить
доступ к корню дерева документов, воспользовавшись следующими именами:
$„SERVER['DOCUMENT-ROOT1]
$DOCUMENT-ROOT
$НТТР_SERVER—VARS['DOCUMENT-ROOT']
Глава 2. Сохранение и восстановление данных
83
Как и для данных формы, первый стиль является наиболее предпочтительным.
Вы можете также задать абсолютный путь к файлу. Этот путь ведет от корневого ка-
талога (/ в системе UNIX и, как правило. С: \ в системе Windows). На используемом на-
ми сервере на базе UNIX таким путем будет /home/book/orders. Проблема, связанная с
подобным представлением пути, особенно если вы размещаете собственный сайт на
чужом сервере, заключается в том, что абсолютный путь может меняться. Мы однаяоды
убедились в этом на собственном горьком опыте после того, как пришлось изменять
абсолютные пути во множестве сценариев, когда системные администраторы приняли
“безобидное” решение без предупреждения изменить структуру каталогов.
Если путь не указан, система создает файл или будет его искать в том же каталоге,
в котором находится сам сценарий. Действия системы будут иными, если среда РНР
функционирует в рамках той или иной оболочки CGI (Common Gateway Interface —
общий шлюзовой интерфейс) и зависит от конфигурации сервера.
В среде UNIX в качестве разделителя каталогов используется левая косая черта
(/). На Windows-платформах можно применять как левую, так и обратную косую чер-
ту (\). Если вы используете обратную косую черту, то она должна быть отменена (по-
мечена как специальный символ), чтобы функция fopen смогла правильно ее интер-
претировать. С этой целью перед ней достаточно просто поместить еще один символ
обратной косой черты, например, так:
$fp = fopen)"$DOCUMENT_ROOT\\..\\orders\\orders.txt", 'w');
Обратную косую черту в пути применяют лишь очень немногие программисты,
поскольку в результате код сможет функционировать только в Windows-средах. Если
же отдать предпочтение левой косой черте, то код может свободно работать в любой
среде.
Второй параметр функции fopen () — это режим файла, этот параметр должен
иметь строковый тип. Передаваемая функции строка указывает, что вы намерены
делать с файлом. В данном случае в функцию fopen () передается параметр 'w', что
означает открытие файла для записи. Режимы файла перечислены в табл. 2.1.
Таблица 2.1. Режимы файла для функции fopen()
Режим Значение
г Режим чтения. Открывает файл для чтения, начиная с начала файла.
г+ Режим чтения. Открывает файл для чтения и записи, начиная с начала файла.
w Режим записи. Открываег файл для записи, начиная с начала файла. Если файл уже
существуег, его содержимое удаляется. Если файл не существует, пытается его создать.
w+ Режим записи. Открывает файл для записи и чтения, начиная с начала файла.
Если файл уже существуег, его содержимое удаляется. Если файл не существует,
пытается его создать.
х Режим осторожной записи. Открываег файл для записи, начиная с начала файла.
Если файл уже существует, он не открывается, fopen () возвращает значение
false, а РНР генерируег предупреждение.
х+ Режим осторожной записи. Открывает файл для записи и чтения, начиная с
начала файла. Если файл уже существует, он не открывается, fopen () возвращает
значение false, а РНР генерирует предупреждение.
84
Часть I. Использование РНР
Окончание табл. 2.1
Режим Значение
а Режим добавления. Открывает файл только для добавления (записи), начиная
с конца существующего содержимого, если таковое имеется. Если файл не
существует, пытается его создать.
а+ Режим добавления. Открывает файл для добавления (записи) и чтения, начиная
с конца существующего содержимого, если таковое имеется. Если файл не
существует, пытается его создать.
Ь Бинарный режим. Используется в сочетании с одним из остальных режимов.
Вы можете воспользоваться этим режимом в тех случаях, когда файловая система
различает бинарные и текстовые файлы. Операционная система Windows
различает эти файлы, тогда как UNIX — нет. Разработчики РНР рекомендуют
всегда указывать этот режим для максимальной переносимости. Этот режим
используется по умолчанию.
t Текстовый режим. Используется в сочетании с одним из остальных режимов.
Этот режим актуален только для Windows-систем. Применять его не
рекомендуется за исключением случаев, когда выполняется перенос
существующего кода с опцией Ь.
В рассматриваемом примере используемый режим файла зависит от установленной
на сервере системы. Выше была выбрана строка 1 w1, которая позволяет записать в
файл только один заказ. При приеме каждого нового заказа он будет перезаписывать
ранее сохраненный заказ. Скорее всего, это не совсем удачное решение, поэтому целе-
сообразно установить режим добавления (и, как рекомендуется, бинарный режим):
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'ab');
Третий параметр функции f open () является необязательным. Вы можете вос-
пользоваться этим параметром, если требуется искать файл с помощью include_path
(настройка конфигурации РНР; см. приложение А). Если это так, установите значе-
ние третьего параметра равным true. В этом случае отпадает необходимость задавать
имя каталога или путь:
$fp = fopen("orders.txt", 'ab', true);
Четвертый параметр функции fopen() также необязательный. Функция fopen()
позволяет предварять имена файлов наименованием протокола (таким как http: //)
и открывать их в удаленных местоположениях. Некоторые протоколы допускают до-
полнительный параметр. Примеры такого применения функции fopen() рассматри-
ваются в следующем разделе этой главы.
Когда функции fopen () успешно открывает файл, она возвращает указатель на
файл и сохраняет его в специальной переменной, в данном случае $fp. Эта перемен-
ная впоследствии используется для доступа к файлу, когда необходимо выполнить
чтение или запись.
Открытие файлов через FTP или HTTP
С помощью функции fopen () можно открывать для чтения или записи не только
локальные файлы, но и удаленные; при этом используются протоколы FTP (File Trans-
fer Protocol — протокол передачи файлов) и HTTP (Hypertext Transfer Protocol — прото
Глава 2. Сохранение и восстановление данных
85
кол передачи гипертекста). Данную возможность можно запретить, отключив дирек-
тиву allow_url_fopen в файле php. ini. Если возникают проблемы при открытии уда-
ленных файлов с помощью fopen (), просмотрите существующий файл php. ini.
Если используемое имя файла начинается с ftp: //, открывается FTP-соединение
в пассивном режиме с указанным вами сервером и возвращается указатель на начало
файла.
Если используемое имя файла начинается с http://, открывается НТТР-соеди-
нение с указанным вами сервером и возвращается указатель на ответ от сервера.
В случае применения режима HTTP в старых версиях РНР обязательно следует ука-
зывать завершающие символы косой черты в именах каталогов, как показано далее:
http: / /vrwn. example. сот/
но не
http://www.example.com
При указании второй формы адреса (без завершающей косой черты), Web-сервер
обычно использует HTTP-перенаправление, чтобы направить вас по первому адресу
(с косой чертой). Проверьте это в своем браузере.
В версиях РНР, предшествующих 4.0.5, функция fopen () не поддерживает НТТР-
перенаправление, поэтому необходимо указывать URL-адреса (Uniform Resource Lo-
cator — унифицированный указатель информационного ресурса), которые ссылаются
на каталоги с завершающими символами косой черты.
Как и в версиях РНР 4.3.0, вы можете открывать файлы, пользуясь протоколом
SSL (Secure Sockets Layer — протокол защищенных сокетов), после того, как скомпи-
лируете пакет OpenSSL, активизируете его поддержку и будете начинать имена фай-
лов с https: //.
Помните, что имена доменов в URL-адресах нечувствительны к регистру, в то
время как пути и имена файлов могут зависеть от регистра.
Проблемы, возникающие при открытии файлов
Типичная ошибка, которую вы можете совершить при открытии файла, — это от-
сутствие разрешений на чтение этого файла или на запись в него. (Как правило, дан-
ная ошибка возникает в средах Unix-подобных операционных систем, хотя иногда с
ней можно столкнуться и в Windows-средах.) В таком случае РНР выводит соответст-
вующее предупреждение, подобное показанному' на рис. 2.2.
В случае получения подобного сообщения об ошибке необходимо убедиться, име-
ет ли пользователь, от имени которого выполняется сценарий, право доступа к фай-
лу, которым вы пытаетесь воспользоваться. В зависимости от настройки сервера,
сценарий может выполняться либо от имени пользователя Web-сервера, либо от
имени владельца каталога, в котором хранится сценарий.
В большинстве систем сценарий выполняется под именем пользователя Web-
сервера. Если сценарий находится в каталоге, например, ~/public_html;chapter02/
системы UNIX, вы могли бы создать общедоступный для записи каталог для хранения
в нем заказов, набрав следующие команды:
mkdir -/orders
chmod 777 -/orders
86
Часть I. Использование PHP
Рис. 2.2. В случае, когда файл открыть невозможно, РНР выдает
соответствующее предупреждение
Имейте в виду, что каталоги, в которых любой пользователь может записать все,
что угодно, несут в себе потенциальную опасность. У вас не должно быть каталогов,
которые доступны для записи непосредственно из Web-среды. Именно но этой
причине наш катало! orders размещается на два подкаталога выше каталога
public_html. Подробнее вопросы безопасности рассматриваются в главе 13.
Некорректные настройки прав доступа, по-видимому, представляют собой наибо-
лее часто встречающуюся ошибку во время открытия файла, однако она далеко не
единственная. Если файл не может быть открыт, то вы должны об этом знать, дабы не
предпринимать дальнейших попыток считывать из него или записывать в него
данные.
Если вызов функции fopen () завершается неудачей, она возвращает значение
false. Обработку ошибок можно сделать более удобной для пользователя, для чего
потребуется подавить сообщение об ошибке от РНР и вывести собственное, более
осмысленное сообщение:
@ $fp = fopen (” $DOCUMENT_ROOT/../orders/orders . txt.", ‘ ab ' ) ;
if (!$fp)
{
echo ' <pxst.rong>B настоящий момент ваш запрос не может быть обработан. '
.'Пожалуйста, попытайтесь позже. </st.rong></p></body></html>' ;
exi t ;
)
Символ @ перед обращением к функции fopen () указывает РНР на необходимость
подавления любых сообщений об ошибках, генерируемых по результатам вызова
функции. Всегда полезно знать, по какой причине что-то выполняется неправильно,
ио в рассматриваемом случае мы с этой проблемой будем разбираться в другом месте.
Глава 2. Сохранение и восстановление данных
87
Первую строку можно записать и в следующем виде:
$fp = @fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'ab');
Такая форма записи делает ее менее понятной, к тому же затрудняется отладка кода.
Описанный метод представляет собой простейший способ обработки ошибок.
Более подробно обработка ошибок рассматриваются в главе 7.
Оператор if проверяет переменную $fp с целью выяснения, возвратила ли функ-
ция fopen () допустимый указатель файла: если это не так, выводится сообщение об
ошибке, после чего выполнение сценария завершается. Поскольку’ здесь завершается
и страница, обратите внимание на закрывающие HTML-дескрипторы, обеспечиваю-
щие получение правильного HTML-кода.
Вывод, полученный в результате использования изложенного выше подхода, по-
казан на рис. 2.3.
; Файл Правка Вид Пдзеход Закладки Инструменты Окно Справка Debug QA
I ' Назад * Обновить & lW./?torHlhost/phpnivsc)te yl Поиск|
! Автозапчасти от Боба
Результаты заказа
I; Заказ обработан в 1547, 14th June i
; Список вашего заказа
' Заказано товаров 6
• 1 автопокрышек
’ 1 2 бутылок с маслом
• । 3 свечей зажигания
!? Итого по заказу 132 00
J Адрес доставки с Майзскюзльное. ул Апачей, д 39
> ; В настоящий момент ваш запрос не может быть обработан. Пожалуйста, попытайтесь позже.
. , I
Готово ’
Рис. 2.3. Использование собственных сообщений об ошибках
вместо генерируемых РНР позволяет получить более дружествен-
ный интерфейс
Запись в файл
Запись в файл в РНР выполняется сравнительно просто. Для этого можно вос-
пользоваться функцией fwrite () (“file write” — “запись в файл”) или fputs () (“file put
string” — “запись строки в файл”); fputs () — это псевдоним fwritef). Функцию
fwrite () вызывается следующим образом:
fwrite($fp, $outputstring);
Этот вызов указывает РНР на необходимость записи строки из переменной
$outputstring в файл, на который указывает $fp.
88
Часть I. Использование РНР
Новой альтернативой fwrite() является функция f ile_put_contents (), которая
имеет следующий прототип:
int file_put_contents ( string filename,
string data
[, int flags
[, resource context]])
Эта функция записывает строку, передаваемую в параметре data, в файл с именем
filename без необходимости его открытия и закрытия с помощью функций fopen ()
и fclosef). Функция появилась в РНР 5 и к ней имеется связанная функция
file_get_contents (), которая будет обсуждаться далее. Необязательные параметры
flags и context используются, в основном, для записи в удаленные файлы с помо-
щью, например, HTTP или FTP. (Упомянутые выше функции подробно рассматрива-
ются в главе 19.)
Параметры функции fwrite ()
Фактически функция fwrite () принимает три параметра, при этом третий из них
является необязательным. Прототип функции fwrite () имеет следующий вид:
int fwrite(resource handle, string string [, int length])
Третий параметр length задает максимальное число байт, которые требуется за-
писать. Если этот параметр в вызове функции fwrite () присутствует, она будет запи-
сывать строку stringB файл, на который указывает параметр handle, до тех пор, по-
ка не достигнет конца строки или не запишет length байт, в зависимости от того, что
произойдет раньше.
Длину строки в РНР можно получить с помощью функции strlen (), например, так:
fwrite($fp, $outputstring, strlen($outputstring));
Третий параметр может понадобиться при записи в бинарном режиме, поскольку
он помогает избежать некоторых проблем несовместимости между платформами.
Форматы файлов
Когда вы создаете файл данных, подобный используемому в рассматриваемом
примере, выбор формата, в котором данные будут храниться, целиком зависит от вас.
(Тем не менее, если вы планируете использовать файл данных в другом приложении,
возможно, придется учесть особенности интерпретации данных в этом приложении.)
Сейчас необходимо создать строку, которая представляет одну запись в нашем
файле данных. Это можно сделать следующим образом:
$outputstring = $date."\t".$tireqty." автопокрышек^"
.$oilqty." бутылок машинного маслахб”
.$sparkqty." свечей зажигания^?".$total."\t".$address."\n”;
В этом простом примере каждая запись заказа сохраняется в отдельной строке
файла. Мы приняли решение размещать в каждой строке по одной записи в связи с
тем, что в таком случае в качестве простого разделителя строк ис пользуется символ
новой строки. Поскольку символы новой строки невидимы, мы представим их в виде
управляющей последовательности "\п".
Глава 2. Сохранение и восстановление данных
89
Поля данных будут всегда записываться в одном и том же порядке, а в качестве раз-
делителя полей используется символ табуляции. Опять-таки, в связи с тем. что этот
символ невидим, он также будет представлен управляющей последовательностью " \t".
В качестве разделителя можно использовать любой легко читаемый символ.
Разделителем, или ограничителем, должен быть любой символ, который наверня-
ка не будет встречаться в исходных данных, иначе придется подвергнуть исходные
данные дополнительной обработке с целью удаления или преобразования всех вхож-
дений такого ограничителя. Обработка пользовательского ввода рассматривается в
главе 4. Пока же предположим, что никто не будет вводить символы табуляции в бу-
дущую форму- заказа. Поместить символы табуляции или новой строки в одностроч-
ное HTML-поле ввода хоть и трудно, но отнюдь не невозможно.
Использование специального разделителя полей упрощает разделение данных на
отдельные переменные во время повторного чтения. Подробнее этот вопрос рас-
сматривается в главах 3 и 4. Пока же каждый заказ мы будем рассматривать как от-
дельную строку.
После обработки нескольких заказов содержимое файла может выглядеть при-
мерно так, как показано в листинге 2.1.
Листинг 2.1. Файл orders. txt — пример содержимого файла заказов
10:30, 20th June 4 автопокрышек 1 бутылок машинного масла 6 свечей зажигания
^$434.00 22 Short St, Smalltown
10:42, 20th June 1 автопокрышек 0 бутылок машинного масла 0 свечей зажигания
^SIOO.OO 33 Main Rd, Newtown
11:43, 20th June 0 автопокрышек 1 бутылок машинного масла 4 свечи зажигания
^$26.00 127 Acacia St, Springfield
Закрытие файла
По завершении использования файла его следует закрыть. Для этой цели служит
функция f close () (“file close” — “закрыть файл”), вызов которой показан ниже:
fclose($fp);
Эта функция возвращает значение true в случае успешного закрытия файла и
false, если что-то этому помешало. Вероятность ошибки при этом намного меньше,
чем при открытии файла, поэтому в данном случае мы решили не производить про-
верку- результата ее выполнения.
Полная версия сценария processorder. php приведена в листинге 2.2.
Листинг 2.2- processorder .php — финальная версия сценария обработки заказа
<?php
// создать короткие имена переменных
Stireqty = $_POST['tireqty;
$oilqty = $_POST['oilqty;
$sparkqty = $_POST['sparkqty'];
$address = $_POST['address'];
$DOCUMENT_ROOT = $_SERVER[1DOCUMENT_ROOT1];
?>
90
Часть I. Использование PHP
<html>
<head>
<t1Ь1е>Автсзапчасти ст Боба - Результаты заказа</Ь1б!е>
</head>
<body>
<М>Автозапчасти от Боба</Ы>
<Ь2>Результаты заказа</Ь2>
<?php
$date = date('H:i, jS F') ;
echo '<р>3аказ обработан в ';
echo $date;
echo '</p>';
echo 1<р>Список вашего заказа: </p>';
Stotalqty = 0;
Stotalqty = Stireqty $oilqty + Ssparkqty;
echo 'Заказано товаров: '.Stotalqty.'<br />';
iff Stotalqty == 0)
{
echo 'Вы ничего не заказали на предыдущей странице!<Ьг />';
}
else
{
if ( $tireqty>0 )
echo Stireqty.' автопокрышек <br />';
if ( $oilqty>0 )
echo Soilqty. бутылок с маслом<Ьг />';
if ( $sparkqty>0 )
echo Ssparkqty. ' свечей зажиганижЬг />';
}
Stotalamount = 0.00;
define('TIREPRICE', 100);
define('OILPRICE', 10);
define('SPARKPRICE', 4);
Stotalamount = Stireqty * TIREPRICE
+ Soilqty * OILPRICE
+ Ssparkqty * SPARKPRICE;
Stotalamount=number_format(Stotalamount, 2, ' ');
echo '<р>Итого по заказу: '.Stotalamount.'</p>';
echo '<p>Адрес доставки: '.Saddress.'</p>';
Joutputstring = $date."\t"-Stireqty." автопокрышек\1".$oilqty.
" бутылок с маслом\б".Ssparkqty.
" свечей зажигания\б\$".Stotalamount."\t".Saddress."\n";
Глава 2. Сохранение и восстановление данных
91
// открыть файл для добавления
@ $fp = fopen(”$DOCUMENT_ROOT/../orders/orders.txt", 'ab');
if (!$fp)
{
echo '<pxstrong>B настоящий момент ваш запрос не может быть обработан. 1
.'Пожалуйста, попытайтесь позже.</strongx/px/body></html> ' ;
exit ;
}
fwrite($fp, $outputstring, strlen($outputstring));
fclose($fp);
echo '<р>3аказ записан.</p>';
?>
</body>
</html>
Считывание из файла
Уже сейчас клиенты компании “Автозапчасти от Боба” могут отправлять свои за-
казы через Web, однако если сотрудники компании Боба захотят просмотреть заказы,
им придется открывать файлы самостоятельно.
Давайте создадим Web-интерфейс, который позволит служащим компании
“Автозапчасти от Боба” легко читать файлы. Код такого интерфейса приведен в
листинге 2.3.
Листинг 2.3. vieworders .php — интерфейс персонала для просмотра файла заказов
<?php
//создать короткое имя переменной
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT']:
<html>
<head>
<title>ABT03an4acTM от Боба - Заказы клиентов</С1С1е>
</head>
<body>
<Ы>Автозапчасти от Боба</Ы>
<Ь2>Заказы клиентов</Ь2>
<?php
@ $fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'rb');
if (!$fp)
{
echo '<p><strong>HeT ожидающих заказов.'
.'Пожалуйста, попытайтесь позже.</strongx/p>';
exit;
}
while (!feof($fp))
{
92
Часть I. Использование PHP
$order= fgets($fp, 999);
echo $order.'<br />';
fclose($fp);
</body>
</html>
В этом сценарии выполняется последовательность действий, которая упомина-
лась выше: открытие файла, чтение из файла, закрытие файла. Вывод, генерируемый
этим сценарием с использованием файла данных из листинга 2.1, показан на рис. 2.4.
; Файл Правка Вид Переход Закладки Инструменты Сж: Оправка Debug QA
a ‘-it1 —- —-——- - - - - - — __________________-
< » w - 92 , с* htto.//focalhnst/phpmysql3e/chaptErO2/vewoniers,php *! а* Пойен I
Назад Обновить ’
Автозапчасти от Боба
|! Заказы клиентов
L 10 30, 20th June 4 автопокрышек 1 бутылок машинного масла 6 свечей зажигания $434 00 22 Short St, Smalltown
I; 1042, 20th June 1 автопокрышек 0 бутылок машинного масла 0 свечей зажигания $100 00 33 Main Rd, Newtown
р 1143, 20th June 0 автопокрышек 1 бутылок машинного магла 4 свечи зажигания $26 00 Acacia St, Springfield
Готово
Рис. 2.4. Сценарий vieworders .php отображает в окне браузера все
заказы, которые на данный момент сохранены в файле orders. txt
Теперь подробно рассмотрим функции, используемые в этом сценарии.
Открытие файла для чтения: функция fopen ()
И снова мы открываем файл с помощью функции fopen(). На этот раз файл от-
крывается только для чтения, поэтом)' используется режим файла ' г ':
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'rb');
Как узнать, где остановиться: функция f eof ()
В этом примере мы используем цикл while для чтения из файла до тех пор, пока
не будет достигнут конец файла. Цикл while проверяет, достигнут ли конец файла, с
помощью функции f eof ():
while (!feof($fp))
Глава 2. Сохранение и восстановление данных
93
Функция feof () принимает один параметр, и в качестве которого передается ука-
затель файла. Она будет возвращать значение true, если указатель файла находится в
конце файла. И хотя имя функции может показаться странным, его легко запомнить,
если знать, что feof означает File End Of File (“файл: конец файла”).
В данном случае (и вообще при чтении из файла) считывание продолжается до тех
пор, пока не будет достигнут символ EOF.
Построчное чтение: функции fgets (),
fgetss()и fgetcsv()
В рассматриваемом примере для считывания из файла используется функция
fgets():
Border = fgets($fp, 999);
Эта функция используется для чтения из файла строк по одной за раз. В данном
случае считывание будет выполняться до тех пор, пока не встретится символ новой
строки (\п), символ EOF или из файла не будут прочитаны 998 байт. Максимальная
длина считываемой строки равна указанной длине минус 1 байт.
Существует много различных функций, которые используют для чтения файлов.
Функция fgets () полезна при работе с файлами, содержащими обычный текст, ко-
торый требуется обрабатывать по частям.
Интересной разновидностью функции fgets () является функция fgetssO,
имеющая следующий прототип:
string fgetss(resource fp, int length, string [allowable_tags]);
Эта функция во многом подобна функции fgets () и отличается от нее только тем,
что она будет удаляет любые РНР- и HTML-дескрипторы, обнаруженные в строке.
Если вы хотите сохранить в файле какие-то конкретные дескрипторы, вам следует
поместить их в строку разрешенных дескрипторов allowable__tags. Функцию
fgetss () следует использовать для достижения большей безопасности во время чте-
ния файла, записанного кем-либо другим или содержащего данные, введенные поль-
зователем. Включение в файл HTML-кода без каких-либо ограничений может при-
вести к нарушению тщательно спланированного форматирования. Отсутствие
ограничений на наличие в файле PHP-кода может предоставить злонамеренному
пользователю практически полную свободу действий на вашем сервере.
Функция fgetcsv() представляет собой еще одну разновидность функции
fgets (). Она имеет следующий прототип:
array fgetcsvfresource fp, int length [, string delimiter [, string enclosure]j);
Эта функция разбивает строки файлов при использовании некоторого символа в
качестве разделителя, например, табуляции (как предлагалось ранее) или запятой
(которая обычно применяется в электронных таблицах и других приложениях). Если
требуется восстановить переменные заказа по отдельности, а не иметь дело со стро-
кой текста, следует прибегнуть к услугам функции fgetcsv(). Она вызывается при-
мерно так же, как и функция fgets (), но ей необходимо передать разделитель, кото-
рый служит разделителем полей. Например, оператор
Border = fgetcsvf$fp, 100, "\t”);
94
Часть I. Использование PHP
извлекает строку из файла и разделяет ее при каждом обнаружении символа табуляции
(\ t). Полученные при этом строки помещаются в массив (в данном примере это мас-
сив $order). Более подробно массивы рассматриваются в главе 3.
Параметр длины length должен быть больше длины самой длинной строки счи-
тываемого файла, выраженной в символах.
Параметр вложения enclosure используется для описания символов, в которые
заключаются каждое поле в строке. Если эти символы не заданы, по умолчанию при-
нимается символ " (двойные кавычки).
Чтение всего файла: функции readfile (),
fpassthru() и file()
Вместо чтения по одной строке из файла за раз, можно прочитать весь файл как
единое целое. Существуют четыре способа выполнения такого считывания.
Первый способ предусматривает использование функции readfile(). Весь при-
веденный выше сценарий можно заменить одной строкой:
readfile("$DOCUMENT_ROOT/../orders/orders.txt");
Обращение к функции readfile () открывает файл, отображает его содержимое в
стандартном выводе (окне браузера), а затем закрывает файл. Прототип этой функ-
ции readfile () выглядит следующим образом:
int readfile(string filenamel, int use_include_path[, resource context]] );
Необязательный второй параметр use_include_path указывает, должен ли РНР
при поиске файла использовать путь, хранящийся в include_path. и действует так
же, как и в функции fopen (). Третий необязательный параметр context использует-
ся, только если файл открыт удаленно, например, через HTTP; такое использование
этой функции будет рассмотрено в главе 19. Функция readfile () возвращает общее
количество байт, считанных из файла.
Второй способ предполагает применение функции fpassthru (). Сначала необхо-
димо открыть файл с помощью функции fopen(). Затем можно передать указатель
файла в функцию fpassthru (), которая загрузит содержимое файла, начиная с пози-
ции, заданной указателем, в стандартный вывод. По окончании этой операции функ-
ция закрывает файл.
Представленный выше сценарий можно заменить функцией fpassthru () следую-
щим образом:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'rb');
fpassthru($ fp);
Функция fpassthru () возвращает значение true, если чтение прошло успешно, и
false — в противном случае.
Третья возможность считывания всего файла предусматривает использование
функции file (). Эта функция идентична функции readfile () за исключением того,
что вместо отображения файла в стандартном выводе она преобразует его в массив.
Более подробно данная функция будет рассматриваться во время изучения массивов в
главе 3. А пока просто отметим, что эту функцию можно вызвать следующим образом:
$filearray = file("$DOCUMENT_ROOT/../orders/orders.txt");
Глава 2. Сохранение и восстановление данных
95
Эта строка приведет к считыванию всего файла в массив с именем $fliearray.
Каждая строка файла сохраняется в отдельном элементе этого массива. Обратите
внимание, что эта функция не является безопасной в отношении бинарных файлов
(binary-safe).
И, наконец, начиная с версии РНР 4.3.0. вы можете воспользоваться функцией
f ile_get_contents (). Эта функция идентична функции readfile за исключением
того, что она возвращает содержимое файла в виде строки вместо того, чтобы выво-
дить его в окно браузера. Достоинство этой новой функции состоит в том. что она
безопасна в отношении бинарных файлов, что, как уже упоминалось ранее, не свой-
ственно для f ile ().
Чтение символа: функция f getc ()
Еще одна возможность обработки файлов состоит в чтении из файла по одному
символу за раз. Это реализуется с помощью функции fgetc? (“file get character” —
“получить символ из файла”). В качестве своего единственного параметра она при-
нимает указатель файла и возврашает следующий символ из файла. Никт while в на-
шем первоначальном сценарии можно заменить циклом, в котором используется
функция fgetc ():
while (Ifeof($fp))
{
$char = fgetc($fp);
if (!feof($fp))
echo ($char=="\n" ? '<br />': $char);
)
С помощью функции fgetc () этот код считывает из файла по одномз символу за
раз и сохраняет его в переменной $char, пока не будет достигнут конец файла. Затем
выполняется небольшая дополнительная обработка с целью замены текстовых сим-
волов конца строки \п HTML-разделителями строк <Ъг />.
Это делается лишь для хорошего форматирования. Если попытаться вывести
файл в браузере с символами новой строки между записями, весь файл будет пред-
ставлен в виде одной строки. (Попытайтесь сделать это и посмотрите. что получит-
ся.) Web-браузеры не визуализируют пробельные символы наподобие символов новой
строки, поэтому они должны заменяться HTML-дескрипторами, в частности. <br />.
Для изящного решения данной задачи используется тернарная операция.
Незначительный побочный эффект использования функции fgecc ( ) вместо
функции fgets () заключается в том, что fgetc () будет возвращать символ EOF. в то
время как fgets () этого не делает. После чтения символа приходится снова прове-
рять feof (), поскольку символ EOF в окне браузера отображаться не должен.
В общем случае считывание файла символ за символом не оправдано, если только
по какой-либо особой причине нам не требуется посимвольная обработка файла.
Чтение строк произвольной длины: функция fread()
Последний способ чтения из файла предусматривает использование функции
f read (), которая читает из файла произвольное количество байт. Эта функция имеет
следующий прототип:
string freadtresource fp, int length);
96
Часть I. Использование PHP
Функция считывает length байт или все байты до конца файла, в зависимости от
того, что произойдет раньше.
Другие полезные файловые функции
Существует ряд других файловых функций, которые время от времени могут ока-
заться полезными. Все они описываются в последующих разделах.
Проверка, существует ли файл: функция f ile_exists ()
Если необходимо проверить, существует ли тот или иной файл, не открывая его,
можно воспользоваться функцией f ile_exists (), как показано в следующем примере:
if (file_exists("$DOCUMENT_ROOT/../orders/orders.txt"))
echo 'Имеются заказы, ожидающие обработки.';
else
echo 'В настоящий момент заказов нет.';
Выяснение размера файла: функция filesize ()
Размер файла можно определить с помощью функции filesize ():
echo filesize("$DOCUMENT_ROOT/../orders/orders.txt");
Она возвращает размер файла, выраженный в байтах. Эта функция может приме-
няться в сочетании с функцией freed () для считывания всего файла (или некоторой
его части). Весь разработанный нами выше сценарий можно заменить следующим
кодом:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt“, 'rb');
echo nl2br(fread( $fp, filesize("$DOCUMENT_ROOT/../orders/orders.txt" )));
fclose( $fp );
Функция nl2br () преобразовывает символы \n в выводе на HTML-дескрипторы
<br />.
Удаление файла: функция unlink ()
Если после обработки заказов файл заказов должен быть удален, это можно сде-
лать с помощью функции unlink(). (Функций с именем delete не существует.) На-
пример:
unlink("$DOCUMENT_ROOT/../orders/orders.txt");
Эта функция возвращает значение false, если файл не может быть удален. Как
правило, это происходит при недостаточном уровне прав доступа к файлу или если
файл вообще не существует.
Перемещение внутри файла: функции rewind (),
fseek()и ftell()
Проверять и манипулировать позицией внутри указателя файла можно с помощью
функций rewind (), fseek() и ftell ().
Глава 2. Сохранение и восстановление данных
97
Функция rewind (1 переустанавливает указатель файла на начало файла. Функция
f tell () сообщает в байтах позицию указателя относительно начала файла. Напри-
мер, в нижнюю часть первоначального сценария (перед командой fclose ()) можно
поместить следующие строки:
echo 'Конечная позиция в указателе файла: '.(ftell($fp));
echo ' <br />';
rewind($fp);
echo 'После функции rewind() позиция составляет: '.(ftell($fp));
echo ’<br />';
Вывод в окне браузера будет выглядеть аналогично показанному на рис. 2.5.
Функция fseek() может использоваться для установки указателя файла в некото-
рую конкретную точку внутри файла. Ее прототип имеет вид:
int fseek(resource fp, int offset) [, int whence];
В результате вызова функции fseek () указатель файла fp устанавливается в точку
файла, имеющую смещение offset байт относительно позиции, заданной парамет-
ром whence (откуда). Необязательный параметр whence по умолчанию принимает
значение SEEK_SET, которое фактически означает начало файла. Другими возмож-
ными значениями являются SEEK_CUR (текущее положение указателя файла) и
SEEK_END (конец файла).
Вызов функции rewind)) эквивалентен вызову функции fseek() со смещением,
равным нулю. Например, вы можете использовать функцию fseek () с целью нахож-
дения средней записи в файле или для реализации бинарного поиска. Часто, когда
подобные задачи требуется решать применительно к достаточно сложному, файду
данных, имеет смысл отдать предпочтение базам данных.
Рис. 2.5. После считывания заказов указатель файла попадает на конец фай-
ла; смещение равно 234 байта. В результате вызова функции rewind)) указа-
тель снова устанавливается в нулевую позицию, то есть в начало файла
98
Часть I. Использование РНР
Блокирование файлов
Представьте ситуацию, когда два клиента одновременно пытаются заказать товар.
(Эта ситуация возникает не столь уж редко, особенно когда Web-сайт начинает обра-
батывать трафик существенного объема.) Что произойдет, если один клиент вызовет
функцию fopen () и начнет запись, а затем второй клиент также вызовет функцию
fopen!) и тоже предпримет попытку записи? Каким в результате всего этого окажет-
ся содержимое файла? Будет ли вначале записан первый заказ, а затем второй, или
наоборот? Будет ли записан первый заказ или второй? Либо же содержимое будет
представлять собой нечто практически бесполезное наподобие двух причудливым
образом переплетенных заказов? Ответы на эти вопросы зависят от конкретной
используемой операционной системы, но чаще всего точно ответить на них невоз-
можно.
Во избежание подобных проблем используется механизм блокирования файлов.
В РНР блокирование реализуется с помощью функции flock!). Эта функция должна
вызываться после открытия файла, но перед считыванием данных из этого файла
или их записью в этот файл.
Прототип функции flock () выглядит следующим образом:
bool flock(resource fp, int operation [. int &wouldblock]);
В функцию необходимо передать указатель на открытый файл и константу, пред-
ставляющую вид требуемой блокировки. Функция возвращает значение true, если
блокировка была успешно выполнена, и false — в противном случае. Необязатель-
ный третий параметр должен содержать true, если запрашиваемая блокировка мо-
жет привести к блокированию текущего процесса (то есть, к его ожиданию).
Возможные значения параметра operation (операция) перечислены в табл. 2.2.
Эти возможные значения претерпели некоторые изменения в версии РНР 4.0.1, тем
не менее, в упомянутой таблице представлены оба набора значений.
Таблица 2.2. Значения параметра operation функции f lock()
Значение параметра operation Описание
LOCK_SH (в ранних версиях — 1) Блокировка чтения. Файл может использоваться совместно с другими читающими приложениями.
LCCK.EX (в ранних верс иях — 2) Блокировка записи. Это монопольный режим. Файл не доступен для совместного использования.
LOCK_UN (в ранних версиях — 3) Отмена существующей блокировки.
LOCKJJN (в ранних версиях — 4) Предотвращаются другие попытки блокирования во время выполнения текущего блокирования.
Если вы намереваетесь воспользоваться функцией flock!), ее следует включить
во все сценарии, в которых задействуется данный файл; в противном случае ее при-
менение лишено смысла.
Обратите внимание на то, что функция flock () не работает с системой NFS (Net-
work File System — сетевая файловая система) и другими сетевыми файловыми систе-
Глава 2. Сохранение и восстановление данных
99
мами. Она также не работает с устаревшими файловыми системами, которые не под-
держивают такую блокировку, к примеру FAT (File Allocation Table — таблица разме-
щения файлов). В среде некоторых операционных систем она реализована на уровне
процессов и не будет работать корректно, если вы используете API-интерфейс (Appli-
cation Programming Interface — интерфейс программирования приложений) много-
поточного сервера.
Для использования блокировки в рассматриваемом примере сценарий
processorder .php необходимо изменить:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'ab');
flock($fp, LOCK_EX); // блокирование файла для записи
fwrite($fp, $outputstring);
flock($fp, LOCK_UN); // снятие блокировки на запись
fclose($ fp);
Также потребуется добавить блокировки в сценарий vieworders .php:
$fp = fopen)"$DOCUMENT_ROOT/../orders/orders.txt”, 'r');
flock($fp, LOCK_SH); // блокирование файла для чтения
// чтение из файла
flock($fp, LOCK UN); // снятие блокировки на чтение
fclose($fр);
Теперь код стал более надежным, тем не менее, он все еще не идеален. Что про-
изойдет, если два сценария попытаются одновременно запросить блокировку? Это
привело бы к состоянию состязаний, когда процессы соперничают за установку бло-
кировки, в условиях которого неизвестно, какому из них это удастся, что, в свою оче-
редь, могло бы породить новые проблемы. Значительно больший эффект можно дос-
тичь при использовании одной из систем управления базами данных.
Более рациональный способ обработки:
системы управления базами данных
До сих пор во всех рассмотренных примерах использовались двумерные файлы.
В следующей части книги будет рассматриваться применение MySQL, одной из ши-
роко известных систем управления реляционными базами данных. Вы вправе спро-
сить: “А зачем все это нужно?”
Проблемы, связанные с использованием
двумерных файлов
При работе с двумерными файлами возникает ряд проблем:
Когда двумерные файлы становятся большими, работа с ними существенно за-
медляется.
Поиск конкретной записи или группы записей в двумерном файле затруднен.
Если эти записи упорядочены, для поиска по ключевому полю можно исполь-
зовать какой-либо из видов бинарного поиска в сочетании с применением за-
писей фиксированной длины. Если нужно найти информацию, соответствую-
щую определенному шаблону (например, найти всех клиентов, проживающих в
Киеве), придется прочесть и проверить каждую из записей по отдельности.
100
Часть I. Использование РНР
Решение задачи одновременного доступа может оказаться проблематичным.
Уже было показано, как блокируются файлы, но это может привести к возник-
новению описанного выше состояния состязаний. Кроме того, это может при-
вести к образованию “узкого места” в системе. При достаточно интенсивном
трафике на сайте большой группе пользователей, возможно, придется долго
ждать разблокирования файла, прежде чем они смогут разместить свои заказы.
Если ожидание продлится слишком долго, люди обратятся за покупкой куда-
нибудь в другое место.
Вся рассмотренная до сих пор обработка файлов сводилась к последователь-
ной обработке, по условиям которой считывание начиналось с начала файла и
выполнялось до его конца. При необходимости вставить записи или удалить их
из середины файла (то есть при необходимости реализации произвольного
доступа), это может оказаться затруднительным — в конце концов, придется
прочитать весь файл в память, внести в него необходимые изменения и снова
записать весь файл. При работе с крупными файлами данных этот процесс со-
пряжен с существенными накладными расходами.
Помимо ограничений, налагаемых правами доступа к файлам, не существует
мало-мальски приемлемого способа обеспечения различных уровней доступа к
данным.
Как эти проблемы решаются с помощью систем
управления реляционными базами данных
Системы управления реляционными базами данных (СУРБД) успешно решают все
эти проблемы:
СУРБД могут обеспечить более быстрый доступ к данным, чем двумерные фай-
лы. При этом MySQL, система управления базами данных, описанная в этой
книге, обладает одними из самых высоких показателей производительности
среди всех СУРБД.
В СУРБД можно легко реализовать запрос на извлечения наборов данных, со-
ответствующих определенным критериям.
СУРБД обладают встроенными механизмами обработки параллельных обра-
щений, освобождая программиста от этой обязанности.
СУРБД обеспечивают произвольный доступ к данным.
СУРБД обладают встроенными системами определения прав доступа. MySQL
обладает особенно большими возможностями в этой области.
Возможно, главная побудительная причина использования СУРБД состоит в том,
что все, или, по меньшей мере, большинство, функциональных возможностей, кото-
рыми, по всеобщему мнению, должны обладать системы хранения данных, в ней уже
реализованы. Конечно, можно было бы создать собственную библиотеку РНР-
функций, но зачем же заново изобретать велосипед?
В части II этой книги мы рассмотрим работу реляционных баз данных в целом и
особенно то, как установить и задействовать MySQL для создания Web-сайтов с под-
держкой баз данных.
Глава 2. Сохранение и восстановление данных
101
Если вы разрабатываете относительно простую систему и чувствуете, что полная
функциональность СУРБД вам не нужна, однако не нужна и возня с блокировками и
прочими “прелестями” использования двумерных файлов, возможно вам подойдет
новое расширение РНР под именем SQLite. Это расширение предоставляет полно-
ценный SQL-интерфейс к двумерным файлам. В настоящей книге внимание фокуси-
руется на использовании MySQL, тем не менее, вы можете ознакомиться с дополни-
тельной информацией, касающейся SQLite, по адресам http://sqlite.org' и
http://www.php.net/sqlite.
Дополнительные источники информации
Для получения более подробной информации о взаимодействии с файловой сис-
темой обратитесь к главе 18. Там рассматриваются вопросы изменения прав доступа,
прав владения и имен файлов, работа с каталогами и взаимодействие со средой фай-
ловой системы.
Кроме того, возможно, имеет смысл прочесть раздел интерактивного руково-
дства по РНР, посвященный файловой системе, который доступен по адресу
http://www.php.net/filesystem.
Что дальше
В следующей главе мы рассмотрим, что представляют собой массивы и как их ис-
пользовать при обработке данных в РНР-сценариях.
102
Часть I. Использование РНР
3
Использование
массивов
Зта глава посвящена использованию одной из наиболее важных программных
конструкций — массивов. Переменные, которые рассматривались в предшест-
вующих главах, являются скалярными, и в них хранится единственное значение. Мас-
сив (array) представляет собой переменную, в которой хранится набор, или последо-
вательность, значений. Один массив может содержать множество элементов. Каждый
элемент массива может содержать только одно значение, причем таким значением
может быть текст, число или другой массив. Массив, который содержит другие мас-
сивы, называется многомерным массивом.
РНР поддерживает как численно-индексированные, так и массивы с описатель-
ными индексами (иногда называемые ассоциативными). Вы, скорее всего, уже имели
дело с каким-то языком программирования и, возможно, знакомы с численно-
индексированными массивами, но если вам ранее не приходилось пользоваться язы-
ками РНР или Perl, то, по всей видимости, с ассоциативными массивами вам сталки-
ваться не доводилось. Массивы с описательными индексами предоставляют более
эффективный механизм, нежели с числовыми индексами. Вместо числового индекса
с каждым элементом такого массива может быть связано слово или другая содержа-
тельная информация.
В этой главе продолжается разработка примера сайта компании “Автозапчасти от
Боба”, поставляющей клиентам запасные части для автомобилей; при этом задейст-
вуются массивы, что должно упростить работу с такой повторяющейся информацией,
как заказы клиентов. Кроме того, использование массивов позволит получить более
короткий и гармоничный код для реализации некоторых действий с файлами, кото-
рые выполнялись в предыдущей главе.
В этой главе, помимо прочих, рассматриваются следующие темы:
Численно-индексированные массивы.
Массивы с индексацией, отличной от числовой.
Операции для работы с массивами.
Многомерные массивы.
Сортировка массивов.
Функции для работы с массивами.
Что такое массив
В главе 1 рассматривались скалярные переменные. Скалярная переменная пред-
ставляет собой именованную ячейку памяти, в которой хранится значение; по анало-
гии, массив представляет собой именованную область памяти, в которой хранится
набор значений, что позволяет группировать обычные скалярные значения.
Список товаров, поставляемых компанией “Автозапчасти от Боба”, в нашем при-
мере представлен массивом. На рис. 3.1 показан список трех из этих товаров, храня-
щихся в формате массива, и переменная Sproducts, в которой хранятся эти три зна-
чения. (Вскоре будет показано, как создавать такие переменные.)
Автопокрышки Бутылки машинного масла Свечи зажигания
Продукция
Рис. 3.1. Товары, поставляемые компанией Боба, мо-
гут храниться в массиве
После того, как информация сохранена в виде массива, над ней можно выполнять
полезные действия. Используя конструкции циклов, описанные в главе 1, можно сэ-
кономить усилия, выполняя одни и те же действия над каждым элементом массива.
Весь объем информации можно перемещать как единый блок. В этом случае все зна-
чения могут быть переданы в функцию с помощью одной строки кода. Например,
может понадобиться упорядочить товары по алфавиту. Чтобы сделать это, мы можем
весь массив передать в PHP-функцию sort ().
Хранимые в массиве значения носят название элементов массива. Каждый элемент
массива имеет связанный с ним индекс (называемый также ключом), который исполь-
зуется для доступа к этому элементу.
В большинстве языков программирования массивы имеют числовые индексы, ко-
торые, как правило, начинаются с нуля или единицы.
РНР позволяет использовать в качестве индексов числа или строки. Массивы
можно применять с традиционной числовой индексацией, в то же время допускается
их использование в более полезной и значащей манере через механизм ключей.
(Данный подход должен быть хорошо знаком программистам, которые пользовались
массивами с описательными индексами либо их аналогами в других языках.) В каче-
стве индексов в ассоциативных массивах могут использоваться практически любые
значения, ио, как правило, таковыми являются строки.
Рассмотрение начнем с численно-индексированных массивов, после чего перей-
дем к использованию ключей, определенных пользователм.
Численно-индексированные массивы
Численно-индексированные массивы поддерживаются в большинстве языков про-
граммирования. По умолчанию в РНР эти индексы начинаются с нуля, хотя это мож-
но изменить.
104
Часть I. Использование РНР
Инициализация численно-индексированных массивов
Для создания массива, показанного на рис. 3.1, воспользуйтесь следующей стро-
кой кода:
$products = array('Автопокрышки', 'Бутылки машинного масла', 'Свечи зажигания');
В результате создается массив $products, содержащий три заданных значения:
'Автопокрышки', 'Бутылки машинного масла' и 'Свечи зажигания'. Обратите вни-
мание, что подобно echo, array () в действительности является языковой конструк-
цией, а не функцией.
В зависимости от требуемого содержимого массива, возможно, не возникнет не-
обходимости инициализировать его вручную, как показано в предыдущем примере.
Если в массиве хранятся данные, которые нужны в другом массиве, можно просто
скопировать один массив в другой с помощью операции =.
Если в массиве необходимо хранить возрастающую последовательность чисел, для
автоматического создания такого массива можно воспользоваться функцией range ().
Следующая строка кода создает массив $numbers, содержащий элементы, которые
представляют собой числа от 1 до 10:
$numbers - range(1, 10);
Функция range () может также принимать необязательный третий параметр, ко-
торый позволяет установить шаг между значениями. Например, если необходимо
получить массив нечетных чисел от 1 до 10, следует воспользоваться таким кодом:
$odds = ranged, 10, 2) ;
Функцию range () можно использовать и для символов:
$letters = range('a', 'z');
Если информация хранится в дисковом файле, содержимое массива можно загру-
зить непосредственно из этого файла. Этот процесс будет рассматриваться в разделе
“Загрузка массивов из файлов” далее в текущей главе.
Если данные для вашего массива хранятся в базе данных, вы можете загрузить со-
держимое массива непосредственно из базы данных. Этот процесс рассматривается в
главе 1 I.
Можно также использовать различные функции для извлечения части массива
или для изменения порядка следования его элементов. Некоторые из этих функций
будут рассматриваться в разделе “Другие манипуляции с массивами” этой главы.
Доступ к содержимому массива
Для доступа к содержимому переменной используется ее имя. Если переменная
является массивом, доступ к ее содержимому осуществляется через имя переменной и
ключ, или индекс. Ключ, или индекс, указывает, к каким значениям, хранящимся в
массиве, осуществляется доступ. Индекс задается в квадратных скобках после имени.
Например, чтобы использовать содержимое массива $products, необходимо вве-
сти: $products[0],$products[1] и $products[2 ].
По умолчанию нулевой элемент является первым элементом массива. Эта же схема
нумерации используется в С, C++, Java и ряде других языков программирования, но ес-
ли вы с ней не работали, то чтобы привыкнуть к ней, потребуется некоторое время.
Глава 3. Использование массивов
105
Подобно другим переменным, содержимое элементов массива изменяется с по-
мощью операции =. Так, следующая строка заменяет первый элемент массива
'Автопокрышки' элементом 'Предохранители',
$products[0] = 'Предохранители';
Для отображения содержимого массива можно ввести такой оператор:
echo "$products[0] $products[l] $products[2] $products[3]";
Обратите внимание на то, что синтаксис строк РНР довольно сложный, поэтому
вы вполне можете запутаться с ним. Если вам приходится сталкиваться с неправиль-
ной интерпретацией массивов или других переменных, когда они включены в строку'
с двойными кавычками, можете вынести их за пределы этих кавычек. Применение
более сложного синтаксиса для строк описано в главе 4. Предыдущий оператор echo
будет работать правильно, а далее, в более сложных примерах, вы увидите, что ис-
пользуемые в них переменные вынесены за пределы строк в кавычках.
Подобно другим переменным РНР, массивы не нужно инициализировать или соз-
давать заранее. Они автоматически создаются при первом их использовании.
Следующий код создает этот же массив Sproducts, но без помощи array ():
$products[0] = 'Автопокрышки';
$products[l] = 'Бутылки машинного масла';
$products[2] = 'Свечи зажигания';
Если массив $products еще не существует, первая строка создает новый массив с
только одним элементом. Последующие строки добавляют значения в массив. По ме-
ре добавления новых элементов размер массива соответствующим образом увеличи-
вается. В большинстве других языков программирования возможность динамическо-
го расширения массивов отсутствует.
Использование циклов для доступа к массиву
Поскольку массив индексируется последовательными номерами, для упрощения
отображения его содержимого можно использовать цикл for:
for ( $i = 0; $i < 3; $i++ I
echo "Sproducts[$i] ";
Этот цикл создаст такой же вывод, как и показанный ранее код. однако он намно-
го компактнее, чем любая написанная вручную программа, отображающая каждый
элемент крупного массива. Возможность использования простого цикла для доступа к
элементам — замечательное свойство численно-индексированных массивов.
Можно также воспользоваться циклом f oreach, специально предназначенным для
работы с массивами. Для данного примера упомянутый цикл применяется следующим
образом:
foreach ($products as $current)
echo $current.' ';
Этот код поочередно сохраняет каждый элемент массива в переменной $current
и затем выводит ее.
106
Часть I. Использование РНР
Массивы с различными индексами
При создании массива Sproducts мы предоставили РНР возможность присвоить
каждому' элементу индекс по умолчанию. Это означает, что первый добавленный эле-
мент стал 0-м элементом, второй — 1-м и так далее. РНР поддерживает также массивы,
в которых с каждым значением можно связать (ассоциировать) любой ключ (индекс).
Инициализация массива
Следующий код создает массив, в котором названия товаров используются в каче-
стве ключей, а их цены — в качестве значений.
Sprices = array( 'Автопокрышки'=>100, 'Бутылки машинного масла’=>10,
'Свечи зажигания'=>4 );
Символы, находящиеся между ключом и значением, — ни что иное, как знак ра-
венства, за которым следует знак “больше”.
Доступ к элементам массива
Как и ранее, доступ к содержимому массива осуществляется через имя перемен-
ной и ключ, поэтому к информации, сохраненной в массиве Sprices, можно обра-
титься как к ^prices [ 'Автопокрышки' ],$prices[ 'Бутылки машинного масла']
иSprices[ 'Свечи зажигания' ].
Следующий код создает этот же массив Sprices. Но вместо того, чтобы сразу соз-
давать массив с тремя элементами, сначала создается массив с только одним элемен-
том, а затем в массив добавляются еще два элемента.
Sprices = array] 'Автопокрышки'=>100 i;
Sprices['Бутылки машинного масла'] = 10;
Sprices['Свечи зажигания'] = 4;
Ниже приводится еще один, несколько отличный от предыдущего, однако эквива-
лентный ему фрагмент кода. В этой версии массив вообще не создается явно. Массив
создается в тот момент, когда в него добавляется первый элемент.
Sprices['Автопокрышки'] = 100;
Sprices['Бутылки машинного масла'] = 10;
Sprices['Свечи зажигания'] = 4;
Использование циклов
Поскольку в ассоциативных массивах индексы не являются числами, для работы
с такими массивами невозможно воспользоваться простым счетчиком в цикле for.
В этом случае потребуется применять цикл foreach либо конструкции list О и
each]).
При работе с ассоциативными массивами структура цикла foreach претерпевает
незначительные изменения. Данный цикл можно использовать в точности так, как и
предыдущем примере, либо можно задействовать ключи:
foreach (Sprices as $key => Svalue)
echo $key.'=>'.Svalue.'<br />’,-
Глава 3. Использование массивов
107
Приведенный ниже код выводит содержимое массива $prices с использованием
конструкции each ().
while( $element = each( $prices ) )
{
echo $element[ 'key' ];
echo ' - ' ;
echo $element[ 'value' ];
echo '<br />';
}
Вывод, генерируемый этим фрагментом кода, показан на рис. 3.2.
В главе 1 были рассмотрены циклы while и оператор echo. В приведенном выше
примере кода используется функция each (), которая ранее не встречалась. Эта функ-
ция возвращает текущий элемент массива и делает текущим следующий элемент. По-
скольку функция each () вызывается внутри цикла while, она по очереди возвращает
каждый из элементов массива и прекращает свое выполнение по достижении конца
массива.
Kt Mozilla {BuW 10: 200-И гдас)
-ЙЛ;
Файл. Правка Вид Переход Закладки Инструменты Окно Справка Debug QA
Назад
3- http ://lccatiost4±prnysql3e/chapter03/eadi .php
Обновить
i Автопокрышки - 100
। Бутылки машинного масла - 10
= Свечи зажигания - 4
готово
Рис. 3.2. Функция each () может использоваться для просмотра
массива в цикле
В этом примере кода переменная $element является массивом. При вызове функ-
ции each () она возвращает массив с четырьмя значениями и четырьмя индексами
ячеек массива. Ячейки key и 0 содержат ключ текущего элемента, а ячейки value и 1 —
значение текущего элемента. Хотя не играет роли, какую из них выбрать, предпочти-
тельнее пользоваться именованными ячейками, а не нумерованными.
Это же можно сделать более изящным и привычным способом — воспользоваться
конструкцией list () для разделения массива на набор значений. Два значения, пе-
редаваемые функцией each (), можно разделить следующим образом:
$list( $product, $price ) = each( Sprices );
108
Часть I. Использование PHP
В этой строке кода с помощью функции each () получается текущий элемент из
массива $prices, затем он возвращается в виде массива, после чего текущим стано-
вится следующий элемент. Кроме того, функция list () используется для преобразо-
вания элементов 0 и 1 массива, возвращаемого функцией each (), в две новых пере-
менных с именами $product и $price.
Можно циклически просмотреть весь массив $prices, отображая его содержимое
на экране, с помощью следующего короткого кода:
while ( list( $product, $price ) = each( $prices ) )
echo "$product - $price<br />";
При этом получается вывод, аналогичный сгенерированному' предыдущим сцена-
рием, однако его легче читать, так как функция list () позволяет присваивать имена
переменным.
При использовании функции each () следует помнить, что массив отслеживает те-
кущий элемент. Если в одном и том же сценарии нам необходимо воспользоваться
одним и тем же массивом дважды, потребуется с помощью функции reset () снова
установить текущий элемент на начало массива. Чтобы вновь выполнить цикличе-
ский просмотр массива $prices, следует воспользоваться показанным ниже кодом:
reset($prices);
while ( list( $product, $price ) = each( $prices ) )
echo ”$product - $price<br />”;
В результате текущий элемент будет снова установлен на начало массива, что по-
зволит еще раз выполнить просмотр массива.
Операции для работы с массивами
Существует набор специальных операций, применимых только в отношении мас-
сивов. Большинство из них имеет скалярные аналоги, как можно видеть в табл. 3.1.
Таблица 3.1. Операции для работы с массивами РНР
Операция Название Пример Результат
+ объединение $а + $Ь Объединение $а и $Ь. Массив $Ь добавляется к массиву $а, при этом элементы с конфлик- тующими ключами не добавляются.
— равно $а == $Ь Возвращает true, если массивы $а и $Ь содер- жат одинаковые элементы.
— идентично $а === $Ь Возвращает true, если массивы $аи $Ь содер- жат одинаковые элементы, расположенные в одном и том же порядке.
!= не равно $а != $Ь Возвращает true, если массивы $а и $Ь не со держат одинаковые элементы.
О не равно $а о $Ь То же, что и ! =.
!== не идентично $а !== $Ь Возвращает true, если массивы $а и $Ь не со- держат одинаковые элементы, расположенные в одном и том же порядке.
Глава 3. Использование массивов
109
Большинство операций достаточно очевидны, и только лишь объединение требу-
ет некоторых пояснений. Операция объединения пытается добавить элементы мас-
сива $Ь в конец массива $а. Если ключи элементов в $Ь совпадают с ключами некото-
рых элементов в $а, такие элементы не добавляются. Таким образом, элементы
массива Sa не перезаписываются.
Несложно заметить, что операции для работы с массивами, перечисленные в
табл. 3.1, имеют аналоги среди операций, предназначенных для работы со скалярными
переменными. До тех пор, пока вы помните, что операция + выполняет сложение ска-
лярных типов данных и объединение массивов (даже если вы не интересуетесь ариф-
метикой множеств, которая лежит в основе объединения), поведение операции не
должно вызывать вопросов. Сравнивать массивы с данными скалярных типов нельзя.
Многомерные массивы
Массив не обязательно должен быть простым списком ключей и значений — каж-
дая ячейка массива может содержать другой массив. Таким образом, допускается соз-
дание двумерных массивов. Двумерный массив можно представить себе в виде мат-
рицы, или таблицы, обладающей шириной и высотой, иначе говоря, строками и
столбцами.
Если бы для каждого товара, поставляемого компанией “Автозапчасти от Боба”,
требовалось хранить более одной порции, то в этом случае мы могли бы воспользо-
ваться двумерным массивом.
На рис. 3.3 товары, поставляемые компанией “Автозапчасти от Боба”, представ-
лены в виде двумерного массива, каждая строка которого представляет отдельный
вид товара, а каждый столбец — атрибут хранящегося товара.
Код товара Описание Цена
TIR Автопокрышки 100
OIL Бутылки машинного масла 10
SPK Свечи зажигания 4
Атрибут товара
Рис. 3.3. В двумерном массиве можно
сохранить больше информации о това-
рах, поставляемых компанией “Авто-
запчасти от Боба”
Для записи данных в массив, представленный на рис. 3.3, используя РНР, может
послужить следующий код:
Sproducts = array( array( 'TIR1, 'Автопокрышки', 100 ),
array( 'OIL', 'Бутылки машинного масла', 10 ),
array( 'SPK', 'Свечи зажигания', 4 ) );
Из этого определения видно, что теперь массив Sproducts содержит три массива.
110
Часть I Использование РНР
Вспомните, что для доступа к данным в одномерном массиве необходимо указать
имя массива и индекс элемента. То же самое справедливо и в отношении двумерного
массива, за исключением того, что каждый элемент имеет два индекса — строку и
столбец. (Верхняя строка является строкой с номером 0, а крайний слева столбец —
столбцом с номером 0.)
Для отображения содержимого этого массива можно было бы вручную осущест-
вить доступ к каждому из элементов в следующем порядке:
echo 1|'.Sproducts[01[0].'I'.Sproducts[0][1].' Sproducts[0][2].’|<br />';
echo 1 I’,$products[l)[0].'| '.Sproducts[1j [1] . ' | '.$products[1][2].’ | <br />';
echo ' |'-Sproducts[2][0] . ' | '.Sproducts[2][1] . ’ | ’.Sproducts[2][2] . '|<br />' ;
С другой стороны, для получения тех же результатов можно поместить цикл for
внутрь другого цикла for.
for ( $row = 0; $row < 3; $row++ )
{
for ( $column = 0; Scolumn < 3; $column++ )
{
echo '|'.Sproducts[Srow][$column];
}
echo '|<br />';
}
Обе версии кода создают в окне браузера одинаковый вывод:
|TIR|Автопокрышки j10 0 |
I OIL|Бутылки машинного масла|10!
|SPK|Свечи зажигания|4|
Единственное различие между двумя приведенными примерами состоит в том,
что при использовании второй версии применительно к крупном)' массиву код будет
намного короче.
Возможно, вместо номеров столбцов вы предпочтете создать их имена, как пока-
зано на рис. 3.3. Для сохранения этого же набора товаров при использовании имен
столбцов, как показано на рис. 3.3, применяется следующий код:
Sproducts = array! array! 'Code' => 'TIR',
'Description' => 'Автопокрышки',
'Price' => 100
) ,
array! 'Code' => 'OIL',
'Description' => 'Бутылки машинного масла',
'Price' => 10
) ,
array! 'Code' => 'SPK',
'Description' => 'Свечи зажигания',
'Price' => 4
)
) ;
С этим массивом работать проще, если требуется извлечь из него единственное
значение. Проще запомнить, что описание хранится в столбце Description (Описа-
ние), нежели если оно хранится в столбце с номером 1. При использовании описа-
Глава 3. Использование массивов
111
тельных индексов не приходится запоминать, что элемент хранится в ячейке [х][у].
Данные можно легко найти, обратившись к ячейке с содержательными именами
строки н столбца, которые несут определенную смысловую нагрузку.
Однако при этом теряется возможность применения простого цикла for для по-
следовательного просмотра всех столбцов. Ниже показан один из вариантов кода для
отображения этого массива:
for ( $row = 0; $row < 3; $row++ )
(
echo '|'.Sproducts[$row]['Code1].'|.Sproducts[$row]['Description'].
'|'.Sproducts[$row]['Price'].'|<br />';
}
С помощью цикла for можно просмотреть внешний численно-индексирован-
ный массив Sproducts. Каждая строка массива Sproducts представляет собой массив
с описательными индексами. Используя функции each() и list () в цикле while,
можно просмотреть эти внутренние массивы. Следовательно, внутри цикла for тре-
буется цикл while.
for ( $row = 0; $row < 3; $row++ )
{
while ( list( $key, Svalue ) = each( Sproducts! $row ] ) )
{
echo "|Svalue";
}
echo '|<br />';
}
He обязательно ограничиваться двумя измерениями — так же, как элементы мас-
сива могут содержать другие массивы, эти массивы, в свою очередь, могут содержать
дополнительные массивы.
Трехмерный массив характеризуется высотой, шириной и глубиной. Если вам
удобно представлять двумерный массив в виде таблицы, имеющей строки и столбцы,
представьте себе стопку таких таблиц. Ссылка на каждый элемент такого массива бу-
дет осуществляться по его слою, строке и столбцу.
Если бы Боб разделил поставляемые им товары на категории, для их хранения
можно было бы воспользоваться трехмерным массивом. На рис. 3.4 показаны данные
о товарах, поставляемых компанией “Автозапчасти от Боба”, в виде трехмерного
массива.
В коде, который определяет этот массив, видно, что трехмерный массив пред-
ставляет собой массив, содержащий массив массивов:
Scategories =
) ,
array(array(array! 'CAR_TIR', 'Автопокрышки', 100 ),
array( 'CAR__OIL', 'Бутылки машинного масла', 10
array! 'CAR_SPK', 'Свечи зажигания', 4 )
) ,
array(array( 'VAN_TIR', 'Автопокрышки', 100 ),
array! 'VAN_OIL', 'Бутылки машинного масла', 10
array! 'VAN_SPK', 'Свечи зажигания', 4 )
) ,
112
Часть I. Использование РНР
array(array( 'TRK_TIR', 'Автопокрышки', 100 ),
array) 'TRK_OIL', 'Бутылки машинного масла', 10
),
array) 'TRK_SPK', 'Свечи зажигания', 4 )
)
) ;
Поскольку этот массив имеет только числовые индексы, для отображения его со-
держимого можно воспользоваться вложенными циклами for.
for ( $layer = 0; $layer < 3; Slayer++ )
{
echo "Слой $layer<br />";
for ( $row = 0; $row < 3; $row++ )
{
for ( $column = 0; $column < 3; $column++ )
{
echo '|',$categories[$layer][$row][$column];
}
echo "|<br />";
}
}
Этот способ создания многомерных массивов позволяет получать четырех-, пятн-
или шестимерные массивы. Синтаксис языка не налагает никаких ограничений на
количество измерений, однако человеку трудно представить конструкции, содержа-
щие более трех измерений. Большинство практических задач логически соответству-
ет конструкциям с тремя или меньшим числом измерений.
Атрибуттовара
Рис. 3.4. Этот трехмерный массив позволяет
разделить товары по категориям
Глава 3. Использование массивов
113
Сортировка массивов
Сортировка связанных данных в массиве часто исключительно полезна. Сорти-
ровка одномерного массива достаточно проста.
Использование функции sort ()
Показанный ниже код, в котором используется функция sort (), упорядочивает
массив в алфавитном порядке:
Sproducts = array( 'Бутылки машинного масла', 'Автопокрышки',
'Свечи зажигания' );
sort(Sproducts);
Теперь элементы массива будут расположены в следующем порядке: Автопокрыш-
ки, Бутылки машинного масла,Свечи зажигания.
Значения можно упорядочивать также и в цифровом порядке. При наличии мас-
сива, содержащего цены на товары, поставляемые компанией “Автозапчасти от Бо-
ба”, его можно отсортировать в порядке возрастания числовых значений, например:
$prices = array(100, 10, 4);
sort(Sprites);
Теперь цены будут приведены в следующем порядке: 4, 10, 100.
Обратите внимание на то, что функция sort () чувствительна к регистру, то есть
прописные буквы предшествуют строчным. Так, “А” меньше “Z”, но “Z” меньше “а”.
Кроме того, функция sort () может принимать необязательный второй параметр,
который может быть равен одной из следующих констант: SORT_REGULAR (по умолча-
нию), SORT_NUMERIC или SORT_STRING. Возможность указания типа сортировки по-
лезна, когда выполняется сравнение строк, содержащих числа, скажем, 2 и 12. С точ-
ки зрения чисел 2 меньше 12, однако строка ' 12 ' будет меньше строки ' 2 '.
Использование функций asort () и ksort ()
для сортировки массивов
Если для хранения информации о товарах и ценах используется массив с описа-
тельными индексами, нужно применять другие функции сортировки, обеспечиваю-
щие совместное сохранение ключей и значений во время сортировки.
Следующий код создает массив с описательными индексами, содержащий три то-
вара и связанные с ними цены, а затем сортирует массив в порядке возрастания цен.
Sprites = array( 'Автопокрышки'=>100, 'Бутылки машинного масла'=>10,
'Свечи зажигания'=>4 );
asort(Sprites);
Функция asort () (“array sort” — “сортировка массива”) упорядочивает массив в со-
ответствии со значениями элементов. В данном массиве значениями являются цены,
а качестве ключей выбраны текстовые описания товаров. Если сортировку требуется
выполнить не по ценам, а по описаниям, следует воспользоваться функцией ksort ()
(“key sort” — “ключевая сортировка”), которая, как должно быть понятно, выполняет
сортировку не по значениям, а по ключам. Показанный далее код приведет к упоря-
114
Часть I. Использование РНР
дочению ключей массива в алфавитном порядке: Автопокрышки, Бутылки машинного
масла,Свечи зажигания.
$prices = array( 'Tires'=>100, 'Oil'=>10, 'Spark Plugs'=>4 );
ksort($prices);
Сортировка в обратном порядке
Мы рассмотрели функции sort (), asort () и ksort (). Все эти функции выполня-
ют сортировку массива в порядке возрастания. Каждая из них имеет соответствую-
щую ей функцию, которая выполняет сортировку массива в порядке убывания. Об-
ратными функциями являются, соответственно, rsort (), arsort () и krsort ().
Функции обратной сортировки используются так же, как функции обычной
сортировки. Функция rsort() выполняет сортировку одномерного численно-
индексированного массива в порядке убывания. Функция arsort () выполняет сорти-
ровку одномерного массива с описательными индексами в порядке убывания значе-
ний элементов. Функция krsort () выполняет сортировку одномерного массива с
описательными индексами в порядке убывания ключей элементов.
Сортировка многомерных массивов
Сортировка массивов, имеющих более одного измерения, или в порядке, отличаю-
щемся от алфавитного либо цифрового, более сложна. В РНР имеется возможность
сравнения двух чисел или двух текстовых строк, но следует помнить, что в многомер-
ном массиве каждый элемент является массивом. В РНР отсутствует возможность срав-
нения двух массивов, поэтому для их сравнения необходимо создать некоторый метод.
В большинстве случаев порядок слов или номеров очевиден, но в случае сложных объ-
ектов выполнение сортировки становится более пр< >блематичным.
Определяемые пользователем функции сортировки
Ниже приводится определение ранее использованного двумерного массива.
В этом массиве хранятся коды товаров трех видов, поставляемых компанией “Авто-
запчасти от Боба’’, их названия и цены.
$products = array) array) 'TIR', 'Автопокрышки', 100 ),
array) 'OIL', 'Бутылки машинного масла', 10 ),
array( 'SPK', 'Свечи зажигания', 4 ) );
В каком порядке расположатся значения, если выполнить сортировку этого мас-
сива? Поскольку известно, что представляет данный массив, существует, по меньшей
мере, два полезных порядка сортировки. Товары можно упорядочить в алфавитном
порядке по описаниям или в цифровом порядке по ценам. И то, и другое одинаково
возможно, но в данном случае мы хотим воспользоваться функцией usort () (“user
sort” — “ пользовательская сортировка”) и указать РНР, как следует сравнивать эле-
менты. Для этого потребуется разработать собственную функцию сравнения.
Следующий код выполняет сортировку этого массива в алфавитном порядке по
значению второго столбца, то есть описания.
Глава 3. Использование массивов
115
function compare($x, $y)
{
if ( $x[l] == $y[l] )
return 0; e
else if ( $x[l] < Sy[1] )
return -1;
else
return 1;
}
usort(Sproducts, 'compare');
До сих пор в книге использовались встроенные PHP-функции. Для сортировки
этого массива должна быть определена своя собственная, то бишь, “пользователь-
ская”, функция. Вопросы создания функций будут подробно рассматриваться в гла-
ве 5, а пока мы приводим только краткое описание этого процесса.
Функция определяется с помощью ключевого слова function. Функции необходи-
мо присвоить имя. Имена должны нести смысловую нагрузку, поэтому назовем новую
функцию в текущем примере compare (). Многие функции принимают параметры,
или аргументы. Наша функция compare () принимает два аргумента: х и у. Ее назна-
чение заключается в том, чтобы принять два значения и определить их порядок.
Применительно к рассматриваемому примеру, параметрами х и у будут два масси-
ва внутри основного массива, каждый из которых представляет один вид товара.
Чтобы обратиться к элементу Description массива х, необходимо ввести $х [1 ], по-
скольку Description есть второй элемент в этом массиве, а нумерация начинается с
нуля. Для сравнения элементов Description из массивов, переданных в функцию,
используются переменные $х [ 1 ] и $у [ 1 ].
Когда функция завершает свою работу, она может вернуть ответ в вызвавший ее
код. Эта операция называется возвратом значения. Для возврата значения из функции
служит ключевое слово return. Например, строка return 1; возвращает значение 1
коду, вызвавшему эту функцию.
Чтобы функция compare () могла быть использована в usort (), она должна срав-
нивать х и у. Функция compare () должна возвращать 0, если значение х равно значе-
нию у, отрицательное число, если значение х меньше у, и положительное — если зна-
чение х больше у. Наша функция будет возвращать значения 0, 1 или -1 в зави-
симости от значений х и у. Заключительная строка кода вызывает встроенную
функцию usort () с массивом, в котором нужно выполнить сортировку (Sproducts) и
именем нашей функции сравнения (compare ()).
Если требуется, чтобы массив был отсортирован в другом порядке, можно просто
написать другую функцию сравнения. Для выполнения сортировки по ценам необхо-
димо просмотреть третий столбец массива и создать следующую функцию сравнения:
function compare($х, $у)
{
if ( $х[2] == $у[2] )
return 0;
else if ( $х[2] < Sy[2] )
return -1;
else
return 1;
}
116
Часть I. Использование PHP
При вызове функции usort($products, compare) массив будет упорядочен в по-
рядке возрастания цен.
Символ “и” в имени usort() означает “user” (“пользовательская”), поскольку этой
функции требуется функция сравнения, определяемая пользователем. Версии
uasort () и uksort () функций asort () и ksort () также требуют применения опреде-
ляемых пользователем функций сравнения.
Подобно функции asort (), функция uasort () используется при сортировке мас-
сива с описательными индексами по значениям. Функцию asort следует применять,
если значения являются простыми числами или текстом. Если же значения представ-
лены более сложными объектами, такими как массивы, следует определить функцию
сравнения и использовать функцию uasort ().
Так же, как и функции ksort (), функция uksort () должна использоваться при
сортировке массива с описательными индексами по значениям. Функцию ksort сле-
дует применять, когда значения являются простыми числами или текстом. Если же
значения являются более сложными объектами наподобие массивов, следует опреде-
лить функцию сравнения и воспользоваться функцией uksort ().
Определяемые пользователем функции сортировки
в обратном порядке
Функции sort (), asort () и ksort () имеют соответствующие функции сортиров-
ки в обратном порядке; их имена содержат символ “г”. Определяемые пользователем
функции сортировки не имеют обратных версий, тем не менее, можно выполнять и
обратную сортировку многомерных массивов. Поскольку выбор функции сравнения
остается за вами, напишите функцию сравнения, возвращающую противоположные
значения. Чтобы можно было выполнить обратную сортировку, эта функция долж-
на возвращать значение 1, если х меньше у, и -1, если х больше у. Например:
function reverseCompare($х, $у)
{
if ( $х[2] == $у[2] )
return 0;
else if ( $х[2] < $у[2] )
return 1;
else
return -1;
}
Теперь вызов функции usort($produccs, reverseCompare) приводит к тому, что
элементы массива следуют в порядке убывания цен.
Изменение порядка следования
элементов в массивах
В некоторых приложениях, возможно, потребуется изменить порядок следования
элементов массива каким-то другим способом. Функция shuffled располагает эле-
менты массива в случайном порядке. Функция array_reverse () возвращает копию
массива, в которой все элементы расположены в обратном порядке.
Глава 3. Использование массивов
117
Использование функции shuf fle()
Бобу нужно, чтобы несколько из поставляемых его компанией товаров были пред-
ставлены на титульной странице сайта. Его компания поставляет большой ассорти-
мент товаров, однако он хотел бы, чтобы на титульной странице отображались три
произвольно выбранных товара. Дабы постоянные посетители не скучали, желатель-
но. чтобы при каждом новом посещении сайта выбирались другие три товара. Этой
цели легко достичь, если информация обо всех товарах будет храниться в массиве.
Сценарий, представленный в листинге 3.1, отображает на экране три случайно вы-
бранных рисунка, сортируя элементы массива в случайном порядке, и затем отобра-
жая первые три из них.
Листинг 3.1. bobs front page.php — использование РНР для создания динамической
титульной страницы компании “Автозапчасти от Боба”
<?php
$pictures = array( 'tire.jpg', 'oil.jpg', 'spark_plug.jpg',
'door.jpg', 'steering_wheel.jpg',
’thermostat.jpg’, 'wiper_blade.jpg',
'gasket.jpg', 'brake_pad.jpg');
shuffle($pictures);
?>
<html>
<head>
<01с1е>Автозапчасти от Bo6a</title>
</head>
<body>
<center>
<Ы>Автозапчасти от Eo6a</hl>
<table width = 100%>
<tr>
<?php
for ( $i = 0; $i < 3; $i++ )
{
echo '<td align = "center"ximg src=\"';
echo $pictures[$i];
echo '"width= ”100" height = "100"></td>1;
</tr>
</table>
</center>
</body>
</html>
Поскольку этот сценарий выбирает произвольные рисунки, то практически при
каждой загрузке генерируется отличающаяся страница; одну из них можно видеть на
рис. 3.5.
118
Часть I. Использование РНР
Рис. 3.5. Функция shuffle () позволяет отображать три случайно вы-
бранных товара
В ранних версиях РНР функция shuffle () требовала предварительного задания
начального числа для запуска генератора случайных чисел с помощью функции
srand (). В новой версии РНР этот шаг больше не является необходимым.
Функция shuffle () (“shuffle” — “перетасовать”) не отличалась особой известно-
стью. В более ранних версиях РНР она работала не очень хорошо и часто выдавала
результаты, которые не отличались достаточно высоким уровнем случайности. В вер-
сии РНР 4.2.x для Windows, например, функция shuffle () вообще ничего не пере-
мешивала и выдавала тот же расклад элементов, с которого начинала. В версии
РНР 5, похоже, она работает нормально. Если эта функция по каким-то причинам
важна, протестируйте ее работу на своем сервере перед тем, как использовать ее в
приложениях.
Поскольку в действительности не требуется изменять порядок следования эле-
ментов во всем массиве, того же результата можно достигнуть и с помощью функции
array_rand().
Использование функции array_reverse ()
Функция array_reverse () принимает массив и создает новый массив, элементы
которого расположены в обратном порядке. Например, существуем множество спо-
собов создания массива, содержащего убывающую последовательность чисел от 10 до 1.
Использование функции range () обычно приводит к созданию возрастающей
последовательности; ее можно изменить на убывающую с помощью функций
array_reverse () или rsort (). Либо можно создать такой массив, выбирая по одному
элементу из исходного массива в цикле for:
$numbers = array();
for($i=10; $i>0; $i—)
array_push( $numbers,$i );
Глава 3. Использование массивов
119
Цикл for может выполняться в порядке убывания, как показано в этом примере.
Начальное значение устанавливается большим, в конце каждого цикла операция дек-
ремента - - уменьшает значение счетчика на единицу.
В примере ниже создается пустой массив, а затем к каждому его элементу приме-
няется функция array_push() для добавления нового элемента в конец массива. По-
путно следует отметить, что обратной функцией для array_push() является функция
аггау_рор (). Эта функция удаляет и возвращает один элемент из конца массива.
Можно также воспользоваться функцией array_reverse () для изменения порядка
следования элементов массива, созданного функцией range ().
$numbers = range(1,10);
$numbers = array_reverse($rmmbers);
Обратите внимание на то, что функция array_reverse () возвращает модифици-
рованную копию массива. Если исходный массив больше не нужен, новую копию
можно просто записать поверх исходной.
Если данные должны быть диапазонами целых чисел, обратного порядка можно
добиться, вызвав range () и передав -1 в необязательном третьем параметре:
$numbers = rangedO, 1, -1) ;
Загрузка массивов из файлов
В главе 2 рассматривались вопросы сохранения в файле заказов клиентов. Каждая
строка результирующего файла выглядит приблизительно так:
11:32, 20th June 4 автопокрышек 1 бутылок машинного масла 6 свечей
4>зажигания $434.00 22 Short St, Smalltown
Для обработки или выполнения этого заказа его можно загрузить в массив. Сцена-
рий, приведенный в листинге 3.2, отображает текущий файл заказов.
Листинг 3.2. vieworders .php — использование РНР для отображения заказов
<?php
// создать короткое имя переменной
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
$orders= file("$DOCUMENT_ROOT/../orders/orders.txt");
$number_of_orders = count($orders);
if ($number_of_orders == 0)
(
echo 1<p><strong>HeT ожидающих заказов.
Пожалуйста, попытайтесь позже.</strongx/p> ' ;
}
for ($i=0; $i<$number_of_orders; $i++)
(
echo $orders[$i].'<br />';
}
?>
Этот сценарий генерирует почти такой же вывод, как и показанный на рис. 2.4,
который выдан сценарием из листинга 2.2 (см. главу 2). Однако на сей раз, мы ис-
120
Часть I. Использование РНР
пользовали функцию file(), которая загружает весь файл в массив. Каждая строка
файла становится отдельным элементом массива.
В этом сценарии также применяется функция count () для подсчета количества
элементов в массиве.
Более того, можно каждый раздел строк заказа загрузить в отдельный элемент
массива, чтобы разделы можно было обрабатывать по отдельности или посредством
выбора соответствующего формата придать им более привлекательный вид. Именно
это и делает сценарий, представленный в листинге 3.3.
Листинг 3.3. vieworders2 .php — использование РНР для разделения, форматирования
и отображения заказов компании “Автозапчасти от Боба”
<?php
// создать короткое имя переменной
$DOCUMENT_ROOT = $_SERVER['DOCUMENTJWT’];
<html>
<head>
<С1Р1е>Автозапчасти от Боба - Заказы клиентов</бд.б!е>
</head>
<body>
<Ь1>Автозапчасти от Боба</М>
<Ь2>Заказы клиентов</Ь2>
<?php
// Считывание всего файла.
// Каждый заказ становится элементом массива
$orders= file ("$DOCUMENT_ROOT /../orders/orders.txt");
// Подсчет количества заказов, хранящихся в массиве
$number_of_orders = count($orders);
if ($number_of_orders == 0)
{
echo '<pxstrong>HeT ожидающих заказов.
Пожалуйста, попытайтесь позже.<'strongx/р>';
}
echo "ctable border=l>\n";
echo '<trxth bgcolor = "#CCCCFF\">Дата заказа</ДЬ>
<th bgcolor = "#CCCCFF\">Автспокрышки</бЬ>
<th bgcolor = "#CCCCFF\”>Машинное масло</бЬ>
<th bgcolor = "#CCCCFF\">Свечи зажиганияс/th>
<th bgcolor = "#CCCCFF\“>Bcero</th>
<th bgcolor = "#CCCCFF\">Адрес</th>
<tr>';
for ($i=0; $i<$number_of_orders; $i++)
{
// Разбиение строк
$line = explode! "\t". $orders[$i] );
// Сохранение только количества заказанных товаров
$line[l] = intval( $line[l] );
$line[2] = intval! $line[2] );
$line[3] = intval! $line[3] );
// Вывод заказов
echo "<trxtd>$line[0]</td>
<td align = "right">$line[1]</td>
Глава 3. Использование массивов
121
<td align = ”right">$line[2]</td>
<td align = "right”>$line[3]</td>
<td align = "right”>$line[4]</td>
<td>$line[5]</td>
</tr>”;
}
echo "</table>”;
</body>
</html>
Код, показанный в листинге 3.3, загружает весь файл в массив, однако, в отличие
от примера, приведенного в листинге 3.2, в нем используется функция explode () для
разбиения каждой строки, чтобы перед выводом ее можно было подвернуть опреде-
ленной обработке и форматированию.
Генерируемый этим сценарием вывод показан на рис. 3.6.
Рис. 3.6. После разбиения записей заказа с помощью функции
explode () для большей наглядности каждую часть заказа можно
поместить в отдельную ячейку таблицы
Функция explode () имеет следующий прототип:
array explode(string separator, string string [, int limit])
В предыдущей главе при сохранении этих данных в качестве разделителя исполь-
зовался символ табуляции, поэтому здесь применяется такой вызов:
explode( ”\t", $orders[$i] )
В результате переданная этой функции строка разбивается на части. Каждый сим-
вол табуляции становится разделителем между двумя элементами. Например, строка:
”11:32, 20th June\t4 автопокрышек 1 бутылок машинного маслаХСб свечей
^зажиганияхt$434.00\t22 Short St, Smalltown"
122
Часть I. Использование PHP
разбивается на части " 11: 32, 20th June", “4 автопокрышек", "1 бутылок машинно-
го масла", "6 свечей зажигания", ”$434.00"и"22 Short St, Smalltown".
Обратите внимание на необязательный параметр limit, используется для того,
чтобы ограничить максимальное число частей, на которые разбивается строка.
В данном случае объем обработки не особенно велик. Вместо того чтобы в каждой
строке выводить название товара (автопокрышки, бутылки машинного масла и свечи
зажигания), в ней отображается только заказанное количество каждого из них, а
таблица снабжается строкой заголовка, показывающей, что означает каждое из
значений.
Существует ряд способов возможного извлечения чисел из этих строк. В данном
случае использовалась функция intval (). Как было отмечено в главе 1, эта функция
преобразует тип string в тип integer. Преобразование в этом примере выполняется
достаточно рационально, оно игнорирует такие фрагменты строки, как метка, кото-
рые не могут быть преобразованы в integer. Различные способы обработки строк
рассматриваются в следующей главе.
Другие манипуляции с массивами
До этого момента было рассмотрено лишь около половины функций обработки
массивов. Время от времени полезными могут оказаться и другие функции, которые
будут описаны ниже.
Перемещение внутри массива: функции each (),
current(), reset(), end(), next(), pos()иprev()
Ранее упоминалось, что каждый массив имеет внутренний указатель, который ука-
зывает на текущий элемент массива. Выше мы косвенно задействовали этот указатель
при использовании функции each (), но его можно использовать и манипулировать
им непосредственно.
При создании нового массива текущий указатель инициализируется таким образом,
что он указывает на первый элемент массива. Вызов функции current ($array_name)
возвращает первый элемент.
Вызов функции next () или each () перемещает указатель вперед на один элемент.
Вызов функции each($array_name) возвращает текущий элемент, прежде чем пере-
местить указатель. Функция next () ведет себя несколько иначе — в результате вызова
функции next ($array_name) сначала перемещается указатель, а только после этого
возвращает новый текущий элемент.
Мы уже видели, что функция reset () возвращает указатель на первый элемент
массива. Аналогично, вызов функции end($array_name) перемещает указатель в ко-
нец массива. Функции reset () и end() возвращают, соответственно, первый и по-
следний элементы массива.
Для перемещения в массиве в обратном направлении можно воспользоваться
функциями end () и prev (). Функция prev () является обратной по отношению к
функции next (). Она перемещает текущий указатель на один элемент назад, а затем
возвращает новый текущий элемент.
Глава 3. Использование массивов
123
Например, следующий фрагмент кода отображает элементы массива в обратном
порядке:
$value = end ($array);
while ($value)
{
echo "$value<br />";
$value = prev($array);
)
Например, если массив $array объявлен следующим образом:
$array = array(1, 2, 3);
вывод в окне браузера будет выглядеть так:
3
2
1
Используя функции each (), current (), reset (), end (), next (), pos () и prev (),
вы можете написать свой собственный код, позволяющий перемещаться в массиве в
любом порядке.
Применение любой функции к каждому элементу
массива: функция array_walk ()
Иногда требуется выполнять одинаковые действия над каждым элементом масси-
ва. Делать это позволяет функция array_walk ().
Функция array_walk () имеет следующий прототип:
bool array_walk(array arr, string func, [mixed userdata])
Подобно тому, как мы поступали при вызове функции usort (), с которой уже
приходилось иметь дело, при вызове функции array_walk () предполагается исполь-
зование вашей собственной функции.
Нетрудно убедиться, что функция array_walk () принимает три параметра. Пер-
вый параметр arr — это массив, подлежащий обработке. Второй параметр, func,
представляет собой имя определяемой пользователем функции, которая будет при-
меняться к каждому элементу массива. Третий параметр, userdata, является необяза-
тельным. В тех случаях, когда он используется, он передается определяемой пользо-
вателем функции в качестве параметра. Вскоре будет показано, как он работает.
Подходящей в этом плане функцией, определяемой пользователем, может оказаться
функция, которая отображает каждый элемент в некотором заданном формате.
Следующий фрагмент кода отображает каждый элемент с новой строки, вызывая оп-
ределяемую пользователем функцию my Print () для каждого элемента массива $аггау:
function my_print($value)
{
echo "$value<br />";
}
array_walk($array, 'my_print’);
124
Часть I. Использование PHP
Создаваемая вами функция должна иметь определенную сигнатуру. Для каждого
элемента массива функция array_walk() принимает ключ и значение, хранящиеся в
массиве, и любые данные, которые вы захотите передать в параметре userdata, по-
сле чего она вызывает написанную вами функцию, например, следующую:
yourfunction{value, key, userdata)
В большинстве случаев определенная вами функция использует только значения,
хранящиеся в массиве. В некоторых случаях функции придется передавать некото-
рые данные через параметр userdata.
Иногда наряду со значением того или иного элемента массива интерес может вы-
звать и его ключ. Созданная вами функция может просто игнорировать и ключ, и па-
раметр userdata, как это имеет место в myPrint ().
В качестве несколько более сложного примера создадим функцию, которая меняет
значения в массиве и требует передачи ей некоторого параметра. Обратите внима-
ние, что хотя сам по себе ключ не представляет для нас интереса, тем не менее, он
должен быть задан, чтобы функция могла принять третий параметр.
function my_multiply(&$value, $key, Sfactor)
{
$value *= $factor;
}
array_walk(&$array, 'my_multiply1, 3);
В этом примере мы определяем функцию my_multiply (), которая будет умножать
каждый элемент массива на заданный коэффициент. В данном случае нам необходи-
мо использовать необязательный третий параметр функции array_walk () с тем, что-
бы передать его в функцию my_multiply () и использовать как коэффициент умноже-
ния. Поскольку нам нужен этот параметр, функцию my_multiply() необходимо
определить так, чтобы она принимала три параметра — значение элемента массива
($value), ключ элемента массива ($кеу) и коэффициент умножения ($factor). В дан-
ном случае ключ нам не нужен.
Обратите внимание на то, как мы передаем в функцию параметр $value. Символ
амперсанда (&) перед именем переменной в определении функции my_multiply()
означает, что $value будет передаваться по ссылке. Передача по ссылке позволяет
функции изменять содержимое массива.
Подробнее передача по ссылке описана в главе 5. Вам, возможно, не доводилось
иметь дело с этим термином, но пока достаточно отметить, что для передачи значе-
ния по ссылке перед именем переменной следует поместить символ амперсанда.
Подсчет элементов в массиве: функции count (),
sizeof () и array__count_values ()
В приведенном ранее примере для подсчета количества элементов в массиве зака-
зов использовалась функция count (). Функция sizeof () служит тем же целям. Обе
функции возвращают количество элементов в переданном им массиве. Так, значение
счетчика количества элементов будет равно 1 для обычной скалярной переменной и
О при передаче либо пустого массива, либо переменной, которая не установлена.
Функция array_count_.values () сложнее.
Глава 3. Использование массивов
125
Если вызвать array_count_values ($array). то эта функция подсчитает, сколько
раз каждое уникальное значение встречается в массиве $аггау. (То есть, эта функция
определяет мощность массива.) Она возвращает массив с описательными индексами,
содержащий таблицу частоты использования элементов. Этот массив содержит в ка-
честве ключей все уникальные значения массива $аггау. Каждый ключ имеет число-
вое значение, указывающее, сколько раз соответствующий ключ встречается в масси-
ве $аггау.
Например, следующий код:
$array = array(4, 5, 1, 2, 3, 1, 2, 1);
$ас = array_count._values ($аггау) ;
создает массив $ас, который содержит:
Ключ Значение
4 1
5 1
1 3
2 2
3 1
Этот результат показывает, что ключи 4, 5 и 3 встречаются в массиве $аггау по
одному разу, 1 — три раза, а 2 — дважды.
Преобразование массивов в скалярные переменные:
функция extract ()
Массив с содержательными индексами, содержащий некоторое число пар ключ-
значение, с помощью функции extract () можно преобразовать в набор скалярных
переменных. Эта функция имеет следующий прототип:
extract(array var_array [, int extract_type] [, string prefix] );
Функция extract () предназначена для создания скалярных переменных, имею-
щих те же имена, что и ключи массива. Переменным присваиваются значения, рав-
ные значениям, которые хранятся в массиве.
Ниже показан простой пример.
$array = array( 'keyl' => 'valuel', 'key2' => 'value2', 'кеуЗ' => 'value3');
extract($array);
echo "$keyl $key2 $key3";
Этот фрагмент кода генерирует следующий вывод:
valuel value2 value3
Массив содержал три элемента с ключами keyl, key2 и кеуЗ. Благодаря исполь-
зованию функции extract (), были созданы три скалярные переменные $keyl, $кеу2
и $кеуЗ. Как видно из вывода, значениями переменных $кеу1, $кеу2 и $кеуЗ являют-
ся, соответственно, 'valuel','value2' H'value3'.
Функция extract!) принимает два необязательных параметра: extract_type и
prefix. Переменная extract_type задает функции extract О способ обработки кон-
фликтных ситуаций. Такие ситуации возникают, когда переменная с таким же име-
126
Часть I. Использование РНР
нем, как и у ключа, уже существует. По умолчанию значение существующей перемен-
ной заменяется новым. Допустимые значения параметра extract~type перечислены
в табл. 3,2.
Таблица 3.2. Допустимые значения параметра extract_type функции extract ()
Значение Описание
EXTR_OVERWRITE Перезаписывает существующую переменную в случае конфликта.
EXTR_SKIP Пропускает элемент в случае конфликта.
EXTR_PREFIX_SAME В случае конфликта создает переменную с именем Spref ix_key. В этом случае необходимо передать в функцию параметр prefix.
EXTR_PREFIX_ALL Предваряет имена всех переменных префиксом prefix. В этом случае необходимо передать в функцию параметр prefix.
EXTR_ PREF IX_I WALID Предваряет префиксом prefix те имена переменных, которые в противном случае окажутся недопустимыми (например, чи- словые имена переменных). В этом случае необходимо передать в функцию параметр prefix.
EXTR_IF_EXISTS Извлекает только те переменные, которые уже существуют (то есть заполняет значениями из массива только существующие переменные). Этот параметр появился в версии 4.2.0, и он мо- жет оказаться полезной, например, для преобразования массива $_REQUEST в набор переменных.
EXTR_PREF_IF_EXISTS Создает префиксную версию в тех случаях, когда существует непрефиксная версия. Этот параметр появился в версии 4.2.0.
EXTR_REFS Извлекает переменные как ссылки. Этот параметр появился в версии 4.3.0.
Двумя наиболее полезными значениями являются те, которые определяются по
умолчанию, то есть EXTR_OVERWRITE и EXTR_PREFIX_ALL. Остальные значения могут
использоваться эпизодически, в частности, в тех случаях, когда вам заранее известно,
что конкретные конфликты будут иметь место, и вы хотите, чтобы ключ был пропу-
щен или имел префикс. Ниже показан простой пример использования функции
EXTR_PREFIX_ALL в качестве параметра. Как видите, созданные переменные получают
имена префикс- символ_подчеркивания- имя-ключа.
Sarray = array( 'keyl' => 'valuel', 'key2' => 'value2', 'кеуЗ' => 'value3');
extract($array, EXTR_PREFIX_ALL, 'my_prefix');
echo " $my_pref ix__keyl $my_prefix_key2 $my_pref ix_key3 ’ ;
Этот код снова сгенерирует вывод:
valuel value2 value3
Обратите внимание, что для того, чтобы функция extract!) извлекала элемент,
ключ элемента должен быть допустимым именем переменной, то есть ключи, кото-
рые начинаются с цифр или содержат пробелы, пропускаются.
Глава 3. Использование массивов
127
Дополнительные источники информации
В этой главе освещены, по нашему мнению, наиболее полезные функции для ра-
боты с массивами в РНР. Мы решили не рассматривать все существующие функции
данного типа, поскольку краткое описание каждой из них присутствует в онлайновом
руководстве по РНР, которое находится по адресу http://www.php.net.
Что дальше
В следующей главе рассматриваются функции обработки строк. В ней будут опи-
саны функции, которые выполняют поиск, разделение и объединение строк, а также
весьма мощные функции работы с регулярными выражениями, которые позволяют
выполнять практически любые действия со строками.
128
Часть I. Использование РНР
4
Манипулирование строками
и регулярные выражения
В этой главе мы рассмотрим использование PHP-функций обработки строк для
форматирования и манипулирования фрагментами текста. Мы рассмотрим
также применение строковых функций и функций обработки регулярных выражений
для поиска (и замены) слов, выражений или каких-либо других последовательностей
символов внутри строки.
Эти функции полезны во многих прикладных приложениях. Часто требуется очи-
стить или переформатировать вводимые пользователем данные, предназначенные
для сохранения в базе данных. В частности, функции поиска просто незаменимы при
создании инструментальных средств поиска.
В этой главе, помимо прочих, рассматриваются следующие темы:
Форматирование строк.
Объединение и разделение строк.
Сравнение строк.
Сопоставление и замена подстрок с помощью функций обработки строк.
Использование регулярных выражений.
Пример приложения: интеллектуальная
форма отправки электронной почты
В этой главе строковые функции и функции регулярных выражений! рассматри-
ваются в контексте приложения интеллектуальной формы отправки электронной
почты (Smart Form Mail). Написанные в этой главе сценарии будут добавлены к сайгу
компании “.Автозапчасти от Боба”, который разрабатывается уже на протяжении не-
скольких глав.
На этот раз мы создадим простую и часто используемую форму отзывов клиентов
(так называемую форм}' обратной связи), позволяющую клиентам Боба вводить свои
замечания и пожелания (рис. 4.1). Однако наше приложение будет обладать одним
преимуществом по сравнению со многими другими формами, которые можно встре-
тить в Web. Вместо того чтобы отправлять форм}' по обобщенному адресу электрон-
ной почты наподобие feedback@example.com, мы постараемся сделать этот процесс
более интеллектуальным за счет поиска во вводимых данных ключевых слов и выра-
жений и последующей отправки сообщения электронной почты соответствующему
сотруднику компании “Автозапчасти от Боба”. Например, если сообщение электрон-
ной почты содержит слово “advertising” (“реклама”), данные обратной связи будут
направлены в отдел маркетинга. Если сообщение электронной почты поступает от
самого крупного клиента Боба, оно, по идее, должно быть направлено непосредст-
венно ему как руководителю.
' Файл Правка Вид Переход Закладки инструменты Окно Справка Debug QA j
! * _•' Бйр /,%jcalhosVptTprnysql3e/chapter04/'eeclbsick * м»,..Поиск* ' -И
Назад Обновить ’ - ----< 4
Обратная связь
*• Пожалуйста, сообщите ваше мнение о нашей работе j
И Фамилия, имя !-
j. Адрес e-maJ 1
!' i
!’ Ваше мнение ]
" Отправить мнение ’
j X* Л Готово -ttfc af i
Рис. 4.1. Форма обратной связи с клиентами запрашивает имя, ад-
рес электронной почты и мнение клиента о качестве обслуживания
Мы начнем рассмотрение с простого сценария, показанного в листинге 4.1, до-
полняя его по мере изучения материала.
Листинг 4.1. processfeedback.php — основной сценарий для создания содержимого
формы отправки электронной почты
<?php
// создание коротких имен переменных
$name=$_POST[1 name'];
$email = $__POST [ ' email1 ] ;
$f eedback=$__POST [' feedback' ] ;
$toaddress = 'feedback@example.com';
$subject = 'Обратная связь от Web-сайта';
$mailcontent = 'ФИО клиента: '.$name."\n"
.'Email-адрес клиента: '.$email."\n"
."Комментарии клиента: \n".$feedback."\n";
$fromaddress = 'From: webserver@example.com';
mail($toaddress, $subject, $mailcontent, $fromaddress);
130
Часть I. Использование PHP
<html>
<head>
<Ь1Ь1е>Автоэапчасти от Боба -- Реакция nepenaHa</title>
</head>
<body>
<Ы>Реакция передана*/Ы>
<р>Сообщение с вашей реакцией отправлено.</р>
</body>
<Xhtml>
Обратите внимание, что в общем случае необходимо проверить, заполнил ли
пользователь все обязательные поля формы, воспользовавшись для этой цели, на-
пример, функцией isset (). Для краткости изложения в приведенном сценарии и
других примерах мы будем опускать эту процедуру.
Нетрудно убедиться, что в этом сценарии мы выполнили конкатенацию полей
формы и при помощи PHP-функции mailt) отправили их по электронной почте по
адресу feedback@example.com. До сих пор функция mailt) еще не использовалась,
поэтому сейчас мы рассмотрим, как она работает.
Совершенно очевидно, что эта функция отправляет сообщение электронной поч-
ты. Ее прототип имеет следующий вид:
bool mail(string кому, string тема, string сообщение [,
string лополнительные_заголовки [, string дополнительные_параметры]]);
Первые три параметра являются обязательными и представляют, соответственно,
адрес, по которому должно быть отправлено сообщение, строку темы и содержимое
сообщения. Четвертый параметр может применяться для отправки любых дополни-
тельных допустимых заголовков сообщения электронной почты. За дополнительной
информацией обращайтесь в Internet к документу RFC822. в котором описаны эти
заголовки. (RFC, или Request For Comment (Запрос на комментарий), — источник
многих стандартов Internet. Эти документы будут рассматриваться в главе 19.) В дан-
ном примере четвертый параметр используется для включения в сообщение адреса
"From:" (“От:”) для указания отправителя. Его можно также применять для добавле-
ния таких полей, как "Reply-To:" (“Ответить:”) и "Сс :" (“Копия”). Если вам необхо-
димо ввести более одного дополнительного заголовка, их нужно просто разделить
символами новой строки (\п), как показано в следующем примере:
$additional_headers= "From: webserverGexample.com\n"
.’Reply-To: bob@example.com';
Необязательный пятый параметр может быть использован для передачи парамет-
ра в программу, которая сконфигурирована для отправки электронной почты.
Чтобы можно было пользоваться функцией mailt), при настройке РНР потребует-
ся указать применяемую программу передачи электронной почты. Если в представлен-
ном виде сценарий не работает, еще раз внимательно прочтите приложение А.
На протяжении данной главы мы будем совершенствовать этот основной сцена-
рий за счет добавления соответствующих функций обработки строк и регулярных
выражений РНР.
Глава 4. Манипулирование строками и регулярные выражения
131
Форматирование строк
Часто строки, вводимые пользователем (как правило, в интерфейсе с HTML-
формой), приходится очищать от служебных символов, прежде чем их можно будет
использовать по прямому назначению. В следующих разделах описаны функции, ко-
торые служат для этих целей.
Усечение строки: функции chop (), Itrim () и trim()
Первый шаг в этом направлении предусматривает удаление лишних пробелов. Хо-
тя этот шаг и не обязателен, он может оказаться полезным, если предполагается хра-
нение строки в файле или базе данных либо ее сравнение с другими строками.
Для упомянутой цели в РНР предусмотрены три полезных функции. Функция
trim () служит для приведения вводимых данных в порядок:
$name = trim($name);
$email = trim($email);
$feedback = trim($feedback);
Функция trim() удаляет пробелы в начале и конце строки и возвращает результи-
рующую строку. При этом символами, которые она удаляет, по умолчанию считаются
символы новой строки (\п), возврата каретки (\г), символы горизонтальной (\t) и
вертикальной (\хОВ) табуляций, конца строки (\0) и обычные пробелы. Вы можете
также указать второй параметр, который содержит список символов, подлежащих
удалению из строки, вместо списка по умолчанию. В зависимости от конкретной пре-
следуемой вами цели вместо этой функции можно использовать Itrim () или г trim ().
Обе они аналогичны функции trim!) в том, что принимают редактируемую строку в
качестве параметра и возвращают отформатированную строку. Различие между ними
состоит в том, что функция trim () удаляет пробелы в начале и конце строки, Itrim ()
удаляет пробелы только в начале строки (слева), a rtrim() — только в конце строки
(справа).
Форматирование строк для целей представления
В РНР имеется набор функций, которые можно использовать для переформати-
рования строк различными способами.
Использование HTML-форматирования: функция п12Ьг ()
Функция nl2br () принимает строк}- в качестве параметра и заменяет в ней все
символы новой строки XHTML-дескриптором <Ьг /> (или HTML-дескриптором <br>
в версиях РНР, предшествующих 4.0.5). Данная возможность полезна при выводе
длинной строки в окне браузера. Например, мы воспользовались этой функцией для
форматирования данных обратной связи с клиентами при выводе их на экран:
<р>Ваша реакция (текст показан ниже) отправлена.</р>
<px?php echo n!2br($mailcontent) ; ?> </p>
Вспомните, что HTML игнорирует обычные пробелы, поэтому если вы не от-
фильтруете этот вывод с помощью функции nl2br (), он появится на экране в виде
одной сплошной строки (за исключением символов новой строки, расставленных
самим браузером). Результат можно видеть на рис. 4.2.
132
Часть I. Использование РНР
Рис. 4.2. Использование PHP-функции nl2br () позволяет улуч-
шить представление длинных строк в HTML
Форматирование строк для целей печати
До сих пор мы использовали языковую конструкцию echo для вывода строк в окне
браузера. РНР поддерживает также языковую конструкцию print)), которая выпол-
няет ту же операцию, что и echo, при этом возвращает значение true или false, в
зависимости от того, успешно ли она была выполнена.
Обе эти конструкции выводят строку “как она есть”. С помощью функций
printfO и sprintf () можно выполнять более сложное форматирование. Фактиче-
ски эти функции реализуют одни и те же действия, однако printf () выводит отфор-
матированную строку в окне браузера, a sprintf () только возвращает сформатиро-
ванную строку.
Если вам раньше приходилось программировать на языке С, то вы сразу опреде-
лите, что эти функции концептуально подобны своим С-версиям. Обратите, однако,
внимание на несколько различающийся синтаксис. Если же программировать на С не
доводилось, то чтобы научиться работать с этими функциями, потребуется некоторое
время, однако они этого достойны, поскольку обладают большими возможностями и
пользу от их применения трудно переоценить.
Прототипы этих функций имеют следующий вид:
string sprintf (string формат [, mixed аргументы ...])
void printf (string формат [, mixed аргументы ...])
В качестве первого параметра обеим этим функциям передается строка формата,
описывающая основную форму вывода, в которой вместо переменных используются
коды форматирования. Остальными параметрами являются переменные, которые
будут подставляться в строку формата.
Например, в операторе echo можно указывать переменные, которые должны быть
выведены, например, так:
echo "Общая сумма заказа: $total.";
Глава 4. Манипулирование строками и регулярные выражения
133
Для получения того же результата с помощью функции printfO применяется
следующая конструкция:
printf ("Общая сумма заказа: %s.", Stotal);
Последовательность символов %s в строке формата называется спецификацией
преобразования. Указанная спецификация означает "заменить строкой”. В рассмат-
риваемом случае она будет заменена значением переменной $total, которое интер-
претируется как строка.
Если бы значением, хранящимся в переменной $total, было 12.4, в обоих этих
случаях выводилось бы 12.4.
Преимущество применения функции printfO состоит в том, что в этом случае
можно использовать более удобную спецификацию преобразования, чтобы указать,
что на самом деле переменная $total является числом с плавающей точкой и что
оно должно содержать два знака после десятичной точки, как показано в следую-
щем примере:
printf ("Общая сумма заказа: %.2f", $total);
С учетом заданного форматирования и значения 12.4 переменной $total, на эк-
ран будет выведено 12.40.
Строка формата может содержать несколько спецификаций преобразования. При
использовании п спецификаций преобразования после строки формата необходимо
указать п аргументов. Каждая спецификация преобразования будет замещена пере-
форматированным аргументом в том порядке, в каком они перечислены. Например:
printf ("Общая сумма заказа: %.2f (с доставкой: %.2f)
$total, $total_shipping);
В рассматриваемом случае первая спецификация преобразования использует пе-
ременную $total, а вторая — переменную $total_shipping.
Все спецификации преобразования имеют одинаковый формат:
%['дополняющий_символ][-][ширина][.точность]тип
Все спецификации преобразования начинаются с символа %. Если вы хотите рас-
печатать символ %, следует воспользоваться последовательностью %%.
Дополняюший_символ не относится к числу обязательных параметров. Он будет ис-
пользоваться для дополнения переменной до указанной ширины. Примером может
служить добавление ведущих нулей к такому числу, как значение счетчика. Допол-
няющим символом по умолчанию является пробел. Если вы указываете пробел или 0.
предварять его одинарной кавычкой (1) не нужно. Все остальные символы должны
предваряться одинарной кавычкой.
Символ является необязательным. Он указывает, что данные в поле будут вы-
равниваться по левому краю, а не по правому, как определено по умолчанию.
Параметр ширина указывает функции printf (), сколько места (в символах) необ-
ходимо для подстановки значения переменной в соответствующее место строки.
Параметр точность должен начинаться с десятичной точки. Он определяет коли-
чество отображаемых десятичных знаков после запятой.
Последним параметром спецификации является код типа. Краткое описание ис-
пользуемых кодов типа представлено в табл. 4.1.
134
Часть I. Использование РНР
Таблица 4.1. Коды типов спецификации преобразования
Тип Описание
Ь Интерпретируется как целое число и выводится как двоичное число.
С Интерпретируется как целое число и выводится как символ.
d Интерпретируется как целое число и выводится как десятичное число.
f Интерпретируется как число двойной точности и выводится как число с плаваю- щей точкой.
О Интерпретируется как целое число и выводится как восьмеричное число.
S Интерпретируется как строка и выводится как строка.
и Интерпретируется как целое число и выводится как десятичное число без знака.
X Интерпретируется как целое число и выводится как шестнадцатеричное число с представлением цифр a—f строчными буквами.
X Интерпретируется как целое число и выводится как шестнадцатеричное число с представлением цифр A F прописными буквами.
Вы можете воспользоваться нумерацией аргументов, аналогичной введенной в
версии 4.0.6, а это означает, что не обязательно сохранять тот же порядок их следо-
вания, в каком они были заданы спецификациями преобразований. Например:
printf ("Общая сумма заказа: %2\$.2f. (с доставкой: %l\$.2f) ",
$total_shipping, Stotal);
Вполне достаточно внести позицию аргумента в список, следующий непосредст-
венно за знаком %, после чего следует символ $ — в рассматриваемом нами примере
последовательность 2 \$ означает “заменить вторым аргументом списка”. Этот метод
может использоваться также и для повторяющихся аргументов.
Две альтернативных версии этих функций! называются vprintf () и vsprintf ().
Данные вариации принимают два параметра: строку- формата и массив аргументов, а
не переменное количество параметров.
Изменение регистра строки
Можно также изменять регистр строки. Эта возможность не особенно полезна для
разрабатываемого нами приложения, тем не менее, мы рассмотрим несколько крат-
ких примеров.
Применительно к строке темы. $ subject, используемой для сообщения электрон-
ной почты, регистр можно изменять с помощью нескольких функций. Результаты
использования этих функций кратко описаны в табл. 4.2.
В первом столбце приводятся имена функций, во втором — результат их примене-
ния, в третьем столбце показано, как следует использовать ту или иную функцию в
отношении строки $subject, а в последнем столбце показано, какое значение при
этом возвращается.
Глава 4. Манипулирование строками и регулярные выражения
135
Таблица 4.2. Функции изменения регистра строки и их действие
Функция Описание Использование Результирующее значение
$subject Feedback from web site
strtouppper() Преобразует символы строки в прописные буквы. strtouppper($subject) FEEDBACK FROM WEB SITE
strtolower() Преобразует символы строки в строчные буквы. strtolower($subj ect) feedback from web site
ucfirst() Делает первый символ строки прописным, если он был буквен- ным знаком. ucfirst($subject) Feedback from web site
ucwords () Делает прописным первый символ каждого слова в строке, которая начинается с буквенного знака. ucwords($subj ect) Feedback From Web Site
Форматирование строк для хранения:
функции addslashes () и stripslashes ()
Кроме использования строковых функций для визуального преобразования стро-
ки, некоторые из этих функций можно применять для преобразования строки с це-
лью ее сохранения в базе данных. Хотя процесс записи в базу данных будет обсуж-
даться лишь во второй части книги, в этой главе мы рассмотрим форматирование
строк для последующего их хранения в базе данных.
Некоторые символы вполне могут быть частью строки, однако иногда они стают
источником проблем, в особенности при записи строки в базу данных, поскольку она
может интерпретировать их как управляющие символы. Такими символами являются
кавычки (одинарные и двойные), символы обратной косой черты (\) и символ NULL.
Нужно найти способ маркирования, или отмены (escaping), этих символов, чтобы
такие базы данных, как MySQL, могли понять, что мы имеем в виду литеральное
представление специального символа, а не его интерпретацию в качестве управляю-
щей последовательности. Чтобы отменить эти символы, перед ними необходимо
поместить символ косой черты. Например, " (двойная кавычка) превращается в \".
а \ (обратная косая черта) превращается в \ \ (две обратных косых черты). (Это пра-
вило применяется ко всем специальным символам, поэтому при наличии в строке
символов \\ их следует заменить последовательностью \\\\.)
В РНР существуют две функции, которые специально предназначены для отмены
символов. Прежде чем записывать какие-либо строки в базу данных, их следует пере-
форматировать с помощью функции addslashes (), которая добавляет косые черты.
Например:
$feedback = addslashes($feedback);
Подобно многим другим строковым функциям, addslashes () принимает строку в
качестве параметра и возвращает переформатированную строку.
136
Часть I. Использование РНР
Фактические результаты использования этих функций применительно к строке
можно видеть на рис. 4.3.
Можете протестировать эти функции на своем сервере, дабы получить результат
получше, нежели показанный на рис. 4.4. Если вы видите такой результат, значит,
РНР сконфигурирован на автоматическое добавление косых черт. Данная возмож-
ность носит название магических кавычек и управляется директивой конфигурации
magic_quotes_gpc. В новых версиях РНР эта директива по умолчанию включена. Бу-
квы “gpc” в имени директивы означают GET, POST и cookie. Это значит, что все пере-
менные, поступающие из упомянутых источников, автоматически помещаются в ка-
вычки. Проверить, включена ли директива magic_quotes_gpc, можно с помощью
функции get_magic_quotes_gpc(), которая вернет значение true, если это так.
В случае если режим магических кавычек включен, перед отображением к пользова-
тельским данным необходимо применять функцию stripslashes(), иначе косые
черты будут видны на экране.
Использование магических кавычек позволяет создавать более переносимый код.
Дополнительную информацию об этом режиме можно найти в главе 23.
Объединение и разделение строк
с помощью строковых функций
Часто возникает необходимость просматривать части строк по отдельности. На-
пример, может потребоваться просмотреть слова в предложении (например, для
проверки правописания) или разделить имя домена или адрес электронной почты на
соответствующие компоненты. РНР предлагает несколько строковых функций (и од-
ну функцию регулярного выражения), которые позволяют делать это.
В рассматриваемом примере Боб хочет, чтобы все данные обратной связи с кли-
ентом с адресом bigcustomer. coin поступали непосредственно к нему, поэтому мы
разделим адрес электронной почты, содержащийся в сообщении клиента, на состав-
ляющие его части, чтобы выяснить, не поступило ли оно от самого крупного заказчи-
ка Боба.
га
I Файл Правка Переход ^кладки Инструменты Окно Справка Debug QA
' Л f. г—~——--------------——---------.------——.
' Обновить ** http //tocab>st43hpmysql3e/ch£0erO4^iro
f До вызова функции addslashesQ
•' Что это за сервис "Никаких гарантий'"?
; '• После вызова функции addslashesQ
h Что это за сервис УНикаких гарантий*??
Н
И После вызова функции stripslashesQ
ji Что это за сервис “Никаких гарантий"'?
Готово
Рис. 4.3. После вызова функции addslashes () все кавычки
предваряются символом косой черты. Функция stripslashes ()
удаляет символы косой черты
Глава 4. Манипулирование строками и регулярные выражения
137
Рис. 4.4. Все проблемные символы отменены дважды; это
значит, что режим магических кавычек включен
Использование функций explode (), implode () и join()
Первая функция, explode)), которой можно было бы воспользоваться для этих
целей, имеет следующий прототип:
array explode(string разделитель, string исх_строка [, int лимит]};
Эта функция принимает строку исх_строка и делит ее на части по заданной разде-
лительной строке разделитель. Части строки возвращаются в виде массива. Можно
ограничить число таких частей с помощью необязательного параметра ограничения
лимит, который появился в версии РНР 4.0.1.
Чтобы получить имя домена из адреса электронной почты в сценарии, можно
воспользоваться следующим кодом:
$email._array = explode)'@', Semaii) ;
В результате этого вызова функции explode)) адрес электронной почты делится
на две части: имя пользователя, которое сохраняется в $email_array[0]. и имя доме-
на, которое сохраняется в $email...array [ 1]. Теперь можно проверить имя домена,
чтобы определить источник сообщения клиента и переадресовать сообщение соот-
ветствующем}’ лицу:
if (Semail_array[1] == 'bigcustomer.com')
$toaddress = 'bob@example.com';
else
$toaddress = 'feedback@example.com';
Если имя домена содержит прописные буквы, показанный подход не работает.
Этой проблемы можно избежать, если преобразовать все буквы имени домена в про-
писные или строчные, а затем уже выполнить проверку:
$email_array[1] - strtolower ($email_array[1]);
Эффект, противоположный действию функции explode (), достигается с помо-
щью функций implode () или join)), которые идентичны. Например:
$new_email = implode)'@', $email_array);
138
Часть I. Использование РНР
Приведенный выше оператор принимает элементы из массива $email_array и
объединяет их со строкой, переданной в первом параметре. Вызов этой функции во
многом подобен вызову explode (). но ее действие противоположно.
Использование функции strtok ()
В отличие от функции explode (), которая позволяет за один вызов делить на час-
ти сразу всю строку, функция strtok () (“string token" — “лексема строки”) дает воз-
можность получить фрагменты (называемые лексемами) строки, по одному за вызов.
Эта функцию следует рассматривать как полезную альтернативу функции explode ()
при обработке слов из строки по одному за раз.
Прототип функции strtok () выглядит следующим образом:
srting strtok(string исх_строка, string разделитель};
В качестве разделителя разделитель можно использовать символ или строку сим-
волов, однако следует иметь в виду, что строка ввода будет разделяться по каждом}' из
символов разделителя, а не по всей строке разделителя (как имеет место в функции
explode ()).
Вызов функции strtok () не столь прост, как может показаться, если судить толь-
ко по ее прототипу. Для получения первой лексемы из строки нужно вызвать функ-
цию strtok () со строкой, которую требуется разбить на лексемы, и с разделителем.
Чтобы получить из заданной строки последующие лексемы, достаточно передать
функции один параметр, а именно — разделитель. Функция сохраняет собственный
внутренний указатель на позицию разделителя в строке. Если требуется переустано-
вить указатель, необходимо снова передать строку в функцию.
Как правило, функция strtok () использсется так, как показано ниже:
$token = strtok(Sfeedback,
echo $token,'<br />';
while ($token!= ')
{
$token = strtok(' ');
echo $token.'<br />';
} ;
Как обычно, имеет смысл проверить, например, с помощью функции empty (), за-
полнил ли пользователь некоторые поля в форме. Для краткости изложения эти про-
верки в примерах опускаются.
Приведенный фрагмент кода выводит каждую лексему отзыва клиента в отдель-
ной строке и выполняет цикл, пока лексемы не закончатся. Обратите внимание на то,
что PHP-функция strtok () работает не совсем так, как аналогичная функция в языке С.
При наличии в исходной строке двух идущих подряд разделителей (в данном приме-
ре — двух идущих подряд пробелов) функция strtok () возвращает пустую строку. Ее
невозможно отличить от пустой строки, возвращаемой по достижении конца исход-
ной строки. Кроме того, если одной из лексем является 0, функция также возвращает
пустую строку. В силу упомянутых обстоятельств функция strtok () в РНР не так по-
лезна, как в С. Ее новая версия работает куда лучше, ибо она просто опускает пустые
строки.
Глава 4. Манипулирование строками и регулярные выражения
139
Использование функции substr ()
Функция substr () (“substring” — “подстрока”) позволяет получить доступ к под-
строке между заданными начальной и конечной позициями в исходной строке. Для
нашего примера она не подходит, однако может оказаться полезной, если требуется
получить доступ к некоторым частям строк фиксированного формата.
Функция substr () имеет следующий прототип:
string substr(string строка, int начало[, int длина]);
Эта функция возвращает подстроку из строки строка, начиная с позиции начало
длиной длина.
Рассмотрим примеры использования показанной ниже тестовой строки:
$test = 'Вы прекрасно обслуживаете клиентов';
Если вы вызовете substr (), задав некоторое положительное число в качестве па-
раметра начало (и ничего больше), мы получим строку, начиная с позиции начало и
до конца строки. Например:
substr($test, 3);
возвращает строку прекрасно обслуживаете клиентов. Обратите внимание на тот
факт, что нумерация позиций строки начинается с 0, как в массивах.
Если вызвать функцию substr () только с отрицательным значением параметра
начало, будет получена строка, состоящая из символов, отсчитываемых с конца стро-
ки, длиной, заданной параметром начало. Например, оператор:
substr($test, -8);
возвращает подстроку клиентов.
Параметр длина можно использовать для задания либо количества символов, кото-
рые должны быть возвращены (при положительном значении), либо последнего сим-
вола возвращаемой последовательности (при отрицательном значении). Например:
substr($test, 0, 2);
возвращает первые два символа строки, а именно, подстроку Вы.
Следующий код:
echo substr($test, 3, -9);
возвращает символы, расположенные между третьим символом от начала и девятым
символом от конца строки, то есть прекрасно обслуживаете. Не забывайте, что нуме-
рация символов в строке начинается с 0.
Сравнение строк
До сих пор мы использовали только операцию == для сравнения двух строк на
предмет их равенства. С помощью РНР можно выполнять несколько более сложные
операции сравнения. Мы разделили эти сравнения на две категории: частичное сов-
падение и все прочие. Сначала мы рассмотрим прочие функции, и только после этого
приступим к изучению функций проверки строк на частичное совпадение, которые
нам потребуются для дальнейшей разработки примера с интеллектуальной формой
отправки электронной почты.
140
Часть I. Использование РНР
Упорядочение строк: функции strcmp (),
strcasecmp()и strnatcmp()
Функции strcmp (), strcasecmp () и strnatcmp () служат для упорядочения строк,
которое может понадобиться при сортировке данных.
Прототип функции strcmp () (“string comparing” — “сравнение строк”) имеет вид:
int strcmp(string strl, string str2);
Функция принимает две строки, которые и сравнивает. Если они равны, функция
возвращает значение 0. Если в лексикографическом порядке строка strl следует за
(или больше чем) строкой str2, функция strcmp () вернет число больше 0. Если
строка strl меньше строки str2, функция strcmp () вернет число меньше 0. Эта
функция чувствительна к регистру.
Функция strcasecmp () идентична рассмотренной выше функции и отличается от
нее только тем, что она к регистру не чувствительна.
Функция strnatcmp () (“string natural comparing” — “естественное сравнение строк”) и
ее нечувствительный к регистру аналог srtnatcasecmp () впервые появились в РНР 4.
Эти функции сравнивают строки в “естественном порядке”, который более привычен
для человека. Например, функция strcmp() располагает строку' "2" после строки "12",
поскольку лексикографически первая строка больше второй. Функция strnatcmp () рас-
положила бы эти строки в обратном порядке. Более подробное описание естественного
упорядочения строк доступно на сайте http: / /www. naturalordersort. org /.
Проверка длины строки с помощью функции strlen ()
Длину строки можно проверить с помощью функции strlen(). Если передать
этой функции строку, она вернет ее длину'. Например, оператор strlen (’ hello ’ )
возвращает значение 5.
Эту функцию можно задействовать для проверки правильности вводимых данных.
Рассмотрим пример с адресом электронной почты для создаваемой нами формы, ко-
торый хранится в переменной $email. Один из основных способов проверки пра-
вильности адреса электронной почты, хранящегося в переменной $email, — это про-
верка его длины. По нашему' мнению, минимальная длина адреса электронной почты
составляет шесть символов, например, если полный адрес содержит код страны без
какого-либо домена второго уровня, однобуквенное имя домена и однобуквенный
адрес электронной почты, он может иметь вид a@a.to. Следовательно, программа
может генерировать сообщение об ошибке, если длина адреса оказывается меньше
этого значения:
if (strlen($email) < 6)
(
echo 'Этот адрес электронной почты не является допустимым';
exit; // завершение выполнения РНР-сценария
}
Разумеется, это предельно упрощенный способ проверки правильности информа-
ции. В следующем разделе рассматривается более реалистичный способ.
Глава 4. Манипулирование строками и регулярные выражения
141
Сопоставление и замена подстрок
с помощью строковых функций
На практике часто возникает необходимость проверить наличие конкретной под-
строки в более длинной строке. Обычно такое частичное сопоставление приносит
больше пользы, нежели проверка строк на предмет полного совпадения.
В примере с интеллектуальной формой отправки электронной почты требуется
выполнить поиск ключевых фраз в данных обратной связи, поступающих от пользо-
вателей, и отправить сообщение электронной почты в соответствующее подразделе-
ние компании “Автозапчасти от Боба”. Если мы хотим направлять сообщения элек-
тронной почты, в которых речь идет о магазинах Боба, менеджер\ розничной
продажи, требуется знать, встречается ли в этих сообщениях слово “shop” (“магазин”)
или его производные.
Имея в своем распоряжении рассмотренные выше функции, можно было бы вос-
пользоваться функции explode () или strtokO для получения отдельных слов сооб-
щения, а затем сравнить их, используя операцию == или функцию strcrip ().
Однако то же самое можно сделать с помощью простого вызова одной из функций
сравнения строк или регулярных выражений. Эти функции используются для поиска
определенной последовательности символов внутри строки. Каждую из функций
этих наборов мы рассмотрим по отдельности.
Поиск строк в строках: функции strstr (),
strchr(), strrchr()и stristr()
Для поиска строки внутри другой строки можно использовать любую из функций
strstr(),strchr(), strrchr() или stristr().
Функция strstr () (“string in string” — “поиск строки в строке”) является наиболее
общей и может использоваться для поиска соответствующей строки или символа
внутри более длинной строки. В РНР функция strchr () (“character in string” — “поиск
символа в строке”) полностью совпадает с функцией strstr (), хотя ее имя предпола-
гает, что она применяется для поиска символа в строке, аналогично ее версии в язы-
ке С. В РНР любая из этих функций может применяться для поиска строки внутри
строки, в том числе для поиска строки, состоящей только из одного символа.
Функция str str () имеет следующий прототип:
string strstr(string haystack, string needle);
В качестве параметра этой функции передается строка haystack (“стог сена”), в
которой необходимо выполнить поиск, и строка needle (“иголка”), которую нужно
найти в строке haystack. В случае если строка needle будет обнаружена в строке
haystack, функция возвращает часть строки haystack, начинающуюся со строки
needle; в противном случае она возвращает значение false. Если строка needle
встречается более одного раза, возвращаемая строка будет начинаться с первого
вхождения сроки needle.
Например, в нашем приложении интеллектуальной формы отправки решение, ку-
да направить сообщение электронной почты, можно принять следующим образом:
142
Часть I. Использование РНР
$toaddress = 'feedback@example.com'; // значение по умолчанию
(’/ Изменить значение переменной $toaddress, если критерии удовлетворены
if (strstr($feedback, 'shop'))
Stoaddress = 'retail@example.com';
else if (strstr($feedback, 'delivery'))
Stoaddress = 'fulfillment@example.com';
else if (strstr($feedback. 'bill'))
$toaddress = 'accounts@example.com';
Приведенный код выполняет проверку, присутствуют ли в данных обратной свя-
зи, поступивших от пользователя, определенные ключевые слова, и направляет полу-
ченное сообщение электронной почты соответствующему лицу. Если, например, от-
зыв клиента звучит как “I still haven't received delivery of my last order” ("Я все еще не
получил последний заказ”), в нем будет найдена строка “delivery” (“получил”), и дан-
ные обратной связи будут отправлены по адресу fulf illmentSexample. com.
Существует два варианта функции strstr (). Первый вариант — это функция
stristrO, которая практически идентична предыдущей функции, но не чувстви-
тельна к регистру. Это удобно в рассматриваемом приложении, поскольку клиент
может вводить 'delivery'. 'Delivery' или, скажем, 'DELIVERY'.
Второй вариант — функция strrchrO, которая также идентична функции
strstr (), но с небольшим отличием: она возвращает часть строки haystack, начиная
с последнего вхождения строки needle.
Определение позиции подстроки:
функции strpos () и strrpos ( )
Функции strpos() и strrpos!) действуют аналогично функции strstr () и отли-
чаются от нее только тем, что вместо подстроки они возвращают числовую позицию
строки needle внутри строки haystack. Особенно интересно, что в официальном
руководстве по РНР рекомендуется применять strpos () вместо strstr () по причине
ее высокой скорости.
Функция strpos () (“string position” — “позиция в строке”) имеет следующий про-
тотип:
string strpos(string haystack, string needle [, int offset]);
Возвращаемое целочисленное значение представляет собой первое вхождение
строки needle внутри строки haystack. Как обычно, первый символ занимает нуле-
вую позицию.
Например, следующий код выводит в окне браузера значение 4:
$test = 'Приветствуем на нашем сайте!’;
echo strpos($test, ' е');
В данном случае в качестве строки needle функции передается всего лишь один
символ, однако ею может быть строка любой длины.
Необязательный параметр offset используется для указания позиции внутри
строки haystack, с которой должен начинаться поиск. Например,
echo strpos($test, 'в', 4);
Глава 4. Манипулирование строками и регулярные выражения
143
Этот код выведет в окне браузера значение 8. поскольку РНР начинает поиск сим-
вола “в" с четвертой позиции и, следовательно, не видит этот символ в третьей пози-
ции (считая от нуля).
Функция strrpos () действует почти так же, отличие лишь в том, что она возвра-
щает позицию последнего вхождения строки needle в строке haystack.
В любом из этих случаев, если needle не содержится в строке haystack, функции
strposO и strrpos () возвращают значение false. Это обстоятельство может стать
источником проблем, поскольку в слабо типизированном языке, коим является РНР,
значение false эквивалентно 0, то есть первому символе- в строке.
Вы можете избежать этих проблем, воспользовавшись операцией === для провер-
ки возвращаемых значений:
$result = strpos ($test, ’IT);
if ($result === false)
echo 'He найдено'
else
echo "Найдено в позиции $result”;
Обратите внимание, что этот метод работает только в версии РНР 4 и выше — в
предшествующих версиях можно выполнить только проверку возвращаемого значе-
ния на предмет того, является ли оно строкой (то есть, false).
Замена подстрок: функции str_replace ()
и substr_replace()
Функциональные возможности поиска и замены могут оказаться исключительно
полезными при работе со строками. Поиск и замену можно использовать для персо-
нализации документов, сгенерированных РНР. например, для замены «пате» име-
нем конкретного лица, a «address» — его адресом. Эти функции можно использо-
вать также для цензурирования определенных терминов, например, в приложении
форума или даже в нашем приложении интеллектуальной формы отправки элек-
тронной почты.
С этой целью опять можно воспользоваться строковыми функциями или функ-
циями обработки регулярных выражений.
Чаще всего для замены применяется строковая функция str_replace () (“string
replace” — “замена строки”). Она имеет следующий прототип:
mixed str_replace(mixed needle, mixed new_needle, mixed haystack!, int &count]);
Эта функция заменяет все экземпляры строки needle в строке haystack на строку
new_needle и возвращает новую версию haystack.
Необязательный четвертый параметр, coun t, содержит количество выполненных
замен. Прототип этой функции с четвертым параметром появился в версии РНР 5.
Примечание
Начиная с версии РНР 4.0.5, вы можете передавать все параметры как массивы, благодаря
чему интеллектуальный уровень функции str_replace() заметно возрастает. Вы можете за-
дать массив слов, подлежащих замене, а также массив слов, на которые, соответственно, бу-
дут заменены слова первого массива, и массив строк, к которому эти будут применяться эти
правила. Данная функция вернет массив пересмотренных строк.
144
Часть I. Использование РНР
Например, поскольку' некоторые клиенты могут использовать приложение интел-
лектуальной формы отправки электронной почты для выражения своего недовольст-
ва, они могут прибегать к некоторым довольно-таки выразительным словам. Будучи
программистом, вы легко можете оградить персонал различных подразделений ком-
пании “Автозапчасти от Боба" от всякого рода оскорблений за счет использования
массива $offcolor, который содержит слова подобного рода. Ниже показан пример
вызова функции str_replace () с передачей ей такого массива.
$feedback = str_replace($offcolor, '%!©*', $feedback);
Функция substr_replace () (“substring replace” — “замена подстроки”) использует-
ся для поиска и замены конкретной подстроки на основе ее позиции в строке. Она
имеет следующий прототип:
string substr_replace(string needle, string replacement,
int startf, int length]);
Эта функция заменяет часть строки string строкой replacement. Какую именно
часть — зависит от значений параметра start и от необязательного параметра length.
Значение параметра start представляет собой смещение, с которого начинается
замена. Если оно является нулевым или положительным, смещение определяется от-
носительно начала строки, если же оно отрицательно, то смещение определяется
относительно конца строки. Например, приведенная ниже строка кода будет заме-
нять последний символ в переменной $test символом “ф”:
$test = substr_replace($test, 1ф', -1);
Параметр length является необязательным и задает позицию, в которой РНР
прекращает замену. Если это значение не указано, замена производится с позиции,
определенной параметром start, идо конца строки.
Если значение параметра length равно нулю, строка замены фактически будет
вставлена в строку без перезаписи существующей строки.
Положительное значение параметра 1 ength означает количество символов, кото-
рые должны быть заменены новой строкой, тогда как отрицательное значение пред-
ставляет позицию относительно конца строки, начиная с которой замена символов
прекращается.
Введение в регулярные выражения
РНР поддерживает два стиля синтаксиса регулярных выражений: POSIX и Perl.
Стиль POSIX регулярных выражений встроен в РНР по умолчанию, а стиль Perl будет
доступен после компиляции с библиотекой PCRE (Perl-compatible regular expression —
Perl-совместимые регулярные выражения). Мы рассмотрим более простой стиль
POSIX, но программисты на Perl или читатели, которые желают больше узнать о
PCRE, могут ознакомиться с интерактивным руководством на сайте http: / /php. net.
Примечание
Регулярные выражения POSIX (Portable Operating System Interface for computer environments —
интерфейс переносимой операционной системы) легче воспринимаются и быстрее выполня-
ются, однако они не являются безопасными к бинарным данным.
Глава 4. Манипулирование строками и регулярные выражения
145
До сих пор все действия по сопоставлению с образцами выполнялись с использо-
ванием строковых функций. Мы ограничились изучением случаев точного соответст-
вия строк и подстрок. Несмотря на то что регулярные выражения довольно-таки
трудны для освоения, они исключительно полезны.
Основы
Регулярные выражения предоставляют способ описания шаблона с использовани-
ем текстовых фрагментов. Полное (или литеральное) совпадение, которое можно
было наблюдать до сих пор, является формой регулярного выражения. Например, в
предшествующих примерах отыскивались такие компоненты регулярных выраже-
ний, как “shop” или “delivery”.
В РНР сопоставление регулярных выражений ближе к сопоставлению с помощью
функции strstrO, нежели к сравнению с целью проверки на равенство, поскольку
выполняется сопоставление строки, которая может находиться в любом месте другой
строки. (Одна строка может быть расположена в любом месте другой строки, если
только не указано иначе.) Например, строка “shop” соответствует регулярному выра-
жению “shop”. Она также соответствует регулярным выражениям “h”, “ho” и так далее.
В дополнение к точному сопоставлению символов можно использовать специаль-
ные символы для указания метазначений.
Например, с помощью специальных символов можно указать, что шаблон должен
встречаться в начале или конце строки, что часть шаблона может повторяться или
символы в шаблоне должны иметь конкретный тип. Можно также сопоставлять ли-
теральные значения специальных символов. Ниже мы рассмотрим все эти случаи.
Наборы символов и классы
Использование наборов символов непосредственно расширяет спектр возможно-
стей регулярных выражений по сравнению с выражениями точного сопоставления.
Наборы символов могут использоваться для сопоставления с любым символом кон-
кретного типа — фактически, они являются одним из видов группового символа.
Прежде всего, символ “. ” можно использовать в качестве группового символа для
любого другого одиночного символа, за исключением символа новой строки (\п).
Например, регулярное выражение:
. at
соответствует, в частности, строкам ' cat', ' sat' и 1 mat1.
Этот вид сопоставления групповых символов часто применяется для сопоставле-
ния имен файлов в операционных системах.
Однако, используя регулярные выражения, можно более точно указывать тип
символов, которые нужно сопоставить, и вы фактически можете задавать набор, к
которому должен принадлежать искомый символ. В предыдущем примере регулярное
выражение соответствует строкам ' mat' и 1 cat', но оно соответствует также и стро-
ке '#at'. Если соответствие необходимо ограничить символами набором от а до z,
это указывается следующим образом:
[a-z]at
146
Часть I. Использование РНР
Все, что заключено в специальные символы квадратных скобок [ и ], представля-
ет класс символов, то есть, набор символов, к которому должен принадлежать сопос-
тавляемый символ. Обратите внимание, что заключенное в квадратные скобки вы-
ражение сопоставляется только с одиночным символом.
Набор можно указать в виде списка. Например:
[aeiou]
означает любую гласную английского алфавита.
Можно также задавать диапазон, как это только что было сделано, с помощью
символа дефиса или набор диапазонов:
[a-zA-Z]
Этот набор диапазонов означает любую строчную или прописную букву англий-
ского алфавита.
Наборы можно использовать также для указания символа, который не может быть
элементом набора. Например:
[Aa-z]
соответствует любом)' символу, не содержащемуся в диапазоне a-z. Когда он помещен
внутрь квадратных скобок, символ вставки Л означает “не”. При использовании вне
квадратных скобок он имеет другое значение, о чем речь пойдет несколько позже.
В дополнение к спискам наборов перечисления и диапазонов в регулярных выра-
жениях может быть использован ряд предопределенных классов символов, которые
перечислены в табл. 4.3.
Таблица 4.3. Классы символов, используемые в регулярных выражениях стиля POSIX
Класс Соответствие
[[:alnum:]] Алфавитно-цифровые символы
[[:alpha:]] Буквенные символы
[[:lower:]] Строчные буквы
[[:upper:]] Прописные буквы
[[:digit:]] Десятичные цифры
[[:xdigit:]] Шестнадцатеричные цифры
[[:punct:]] Знаки пунктуации
[[:blanc:]] Символы табуляции и пробелов
[[:space:]] Любые пробельные символы
[[:cntrl:]] Управляющие символы
[[:print:]] Все печатные символы
[[:graph:]] Все печатные символы, за исключением пробельных
Повторение
Часто требуется указать возможность нескольких вхождений в некотором тексте
конкретной строки или класса символов. В регулярном выражении это можно сде-
лать с помощью двух специальных символов. Символ * означает, что шаблон может
Глава 4. Манипулирование строками и регулярные вырвжвния
147
повторяться ноль или большее число раз, а символ + означает, что шаблон повторя-
ется 1 или больше раз. Эти символы должны быть указаны непосредственно после
той части выражения, к которой они применяются. Например,
[[zalnum:]]+
означает “по меньшей мере, один алфавитно-цифровой символ”.
Подвыражения
Часто удобно разделять выражение на подвыражения, чтобы, например, можно
было представить выражение “по меньшей мере, за одной из этих строк следует в
точности одна из тех строк”. Это выражение можно сформулировать с помощью
круглых скобок точно так же, как это делается в арифметических выражениях. На-
пример:
(very )’large
соответствует строкам 'large', 'very large', 'very very large' и так далее.
Подвыражения с подсчетом
Количество повторений какой-либо строки можно указать с помощью числового
выражения, заключенного в фигурные скобки ({}). При этом можно задавать точное
число повторений ({3} означает в точности 3 повторения), диапазон повторений
({2 , 4} означает от 2 до 4 повторений) или указывать открытый диапазон повторе-
ний ({2 ,} означает не менее двух повторений).
Например:
(very ){1, 3}
соответствует строкам 'very', 'very very' и 'very very very'.
Привязка к началу или концу строки
Шаблон [a-z] будет соответствовать любой строке, содержащей строчную букву
английского алфавита. При этом неважно, имеет ли строка длину в один символ, ли-
бо совпадение произошло с одним символом в длинной строке.
Можно также указать, должно ли конкретное подвыражение появляться в начале, в
конце или в начале и в конце строки. Это достаточно удобно, когда необходимо убе-
диться, что в строк}' входит только подстрока, которую вы ищете, и ничего больше.
Если в регулярном выражении конкретной подстроке предшествует символ встав-
ки (Л), это означает, что данная подстрока должна находиться в начале просматри-
ваемой строки, а если в регулярном выражении непосредственно за подстрокой сле-
дует символ доллара ($), то это означает, что данная подстрока должна находиться в
конце просматриваемой строки.
Например, наличию подстроки bob в начале просматриваемой строки соответст-
вует следующее выражение:
ЛЬоЬ
148
Часть I. Использование РНР
Наличию подстроки com в конце просматриваемой строки соответствует такое
выражение:
сот$
И, наконец, любому одиночному символу от а до z, расположенному в отдельной
строке, соответствует приведенное ниже выражение:
Л[a-z]$
Ветвление
Выбор в регулярном выражении представляется с помощью символа вертикаль-
ной черты (|). Например, если требуется сопоставить строки com, edu или net, мож-
но воспользоваться выражением:
(сот)|(edu)|(net)
Сопоставление с литеральными значениями
специальных символов
Если нужно сопоставить один из специальных символов, упомянутых в предыду-
щих разделах, таких как { или $, перед ним необходимо поместить символ косой
черты (\). Если нужно представить символ косой черты, его следует заменить двумя
символами косой черты, т.е. \ \ .
Не забывайте помещать шаблоны регулярных выражений в одинарные кавычки.
Использование PHP-строк в двойных кавычках сопряжено с излишними сложностя-
ми. В РНР для отмены специальных символов (наподобие косой черты) применяется
обратная косая черта (\). Если в шаблоне необходимо выполнить сопоставление с
косой чертой, косых черт должно быть две, что обеспечит литеральное, а не служеб-
ное значение этого символа.
Аналогично, если требуется указать косую черту в PHP-строке в двойных кавыч-
ках, косых черт также должно быть две, по тем же самым причинам. Тот факт, что в
результате применения этих правил PHP-строка, представляющая регулярное выра-
жение с литеральной косой чертой, должна содержать четыре косых черты, многих
сбивает с толку. Интерпретатор РНР преобразует четыре косых черты в две. а затем
интерпретатор регулярных выражений преобразует две косых черты в одну.
Знак доллара ($) также является специальным символом в PHP-сроках в двойных
кавычках и в регулярных выражениях. Чтобы представить литеральный символ $ в
шаблоне, потребуется указать \ \\$. Поскольку' строка находится в двойных кавычках,
интерпретатор РНР преобразует ее в \$, после чего интерпретатор регулярных вы-
ражений будет трактовать ее как символ $.
Краткое описание специальных символов
Краткое описание всех специальных символов приведено в таблицах 4.4 и 4.5.
В табл. 4.4 перечислены значения специальных символов, когда они не заключены в
квадратные скобки, а в табл. 4.5 — значения, которые они приобретают, находясь
внутри квадратных скобок.
Глава 4. Манипулирование строками и регулярные выражения
149
Таблица 4.4. Краткое описание специальных символов, используемых в регулярных
выражениях POSIX за пределами квадратных скобок
Символ Значение
\ Символ отмены специального символа. Соответствие в начале строки.
$ I ( ) Соответствие в конце строки. Соответствие любому символу, за исключением символа новой строки (\п). Начало альтернативной ветви (читается как ИЛИ). Начало подшаблона. Конец подшаблона.
* Повторение ноль или более раз.
+ { } ? Повторение один или более раз. Начало указателя минимального/максимального количества повторений. Конец указателя минимального/максимального количества повторений. Указание подшаблона как необязательного.
Таблица 4.5. Краткое описание специальных символов, используемых в регулярных
выражениях POSIX внутри квадратных скобок
Символ Значение
\ Символ отмены специального символа.
НЕ, только когда используется в начальной позиции.
Используется для указания диапазонов символов.
Использование регулярных выражений в приложении
интеллектуальной формы отправки электронной почты
В приложении интеллектуальной формы отправки регулярные выражения могут
быть применены, по меньшей мере, в двух случаях. Во-первых, для выявления кон-
кретных терминов в данных обратной связи, поступающих от клиентов. Использова-
ние регулярных выражений позволяет несколько упростить решение этой задачи.
Используя строковую функцию для сопоставления строк 'shop', 'customer service'
или ' retail', нам приходилось выполнять три различных поиска. С помощью регу-
лярного выражения можно сопоставить сразу со всеми тремя строками:
shop|customer service|retail
Второе применение — это проверка правильности адреса электронной почты кли-
ента за счет кодирования в регулярном выражении стандартизированного формата
адреса электронной почты. Формат включает некоторые алфавитно-цифровые сим-
волы и символы пунктуации, за которыми следуют символ @, затем идет строка алфа-
витно-цифровых символов или дефисов, затем точка, затем опять алфавитно-
цифровые символы и дефисы и, возможно, дополнительные точки, вплоть до конца
строки. Этот формат можно закодировать следующим образом:
Л[a-zA-Z0-9_\-.]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+$
150
Часть I. Использование РНР
Подвыражение л [a-zA-Z0-9_\- . ] + означает “начало строки, по меньшей мере, с
одной буквы, цифры или символа подчеркивания, или какого-либо их сочетания”.
Символ @ соответствует литералу @.
Подвыражение [a-zA-Z0-9\-] + соответствует первой части имени хоста, которое
включает алфавитно-цифровые символы и дефисы. Обратите внимание на то, что
перед символом дефиса поставлена косая черта, поскольку внутри квадратных скобок
он представляет собой специальный символ.
Комбинация \. соответствует литералу .. Поскольку символ точки используется
вне класса символов, она должна отменяться, дабы соответствовать литеральной
точке.
Подвыражение [a-zA-Z0-9\-\ . ] +$ соответствует оставшейся части имени доме-
на, включающей буквы, цифры, дефисы и дополнительные точки, если они требуют-
ся, вплоть до конца строки.
Несложный анализ показывает, что можно ввести недопустимые адреса элек-
тронной почты, которые, тем не менее, будут соответствовать этому регулярному
выражению. Практически невозможно отловить все такие адреса, но все-таки данное
регулярное выражение позволит хоть немного улучшить ситуацию. Вы можете усо-
вершенствовать это выражение многими способами. Вы, например, можете восполь-
зоваться списком действительных доменов верхнего уровня (top-level domain — TLD).
Однако соблюдайте осторожность при установке различных ограничений, поскольку
проверочная функция, отвергающая хотя бы 1% допустимых данных, вызывает
большее раздражение, чем аналогичная функция, пропускающая 10% недопустимых
данных.
Теперь, когда вы познакомились с регулярными выражениями, перейдем к рас-
смотрению PHP-функций, которые используют эти выражения.
Поиск подстрок с помощью регулярных
выражений
Поиск подстрок — это основное применение только что рассмотренных регуляр-
ных выражений. В РНР существуют две функции, предназначенные для сопоставле-
ния с регулярными выражениями: ereg () и eregi ().
Функция ereg() (“regular expression” — “регулярное выражение”) имеет следую-
щий прототип:
int eregfstring pattern, string search [, array matches]);
Эта функция выполняет поиск в строке search, отыскивая в ней соответствия ре-
гулярному выражению, которое определено в шаблоне pattern. Если соответствия
подвыражений с шаблоном pattern будут найдены, они сохраняются в массиве соот-
ветствий matches, по одному подвыражению в каждом элементе массива.
Функция eregi () идентична предыдущей, за исключением того, что она не чувст-
вительна к регистру.
Мы можем адаптировать интеллектуальную форму отправки электронной почты
под использование регулярных выражений:
Глава 4. Манипулирование строками и регулярные выражения
151
if (!eregi('A[a-zA-Z0-9_\-.]+@[a-zA-Z0-9\-I+\.[a-zA-Z0-9\-.]+$’, $email))
(
echo ’Недопустимый адрес электронной почты. Пожалуйста, 1
.’вернитесь на предыдущую страницу и попытайтесь еще раз.’;
exit;
}
Stoaddress = ’ feedback@example.coni' ; // значение по умолчанию
if (eregi(’shop|customer service|retail', $feedback))
Stoaddress = 'retail@example.com';
else if (eregi('deliver|fulfill', $feedback))
Stoaddress = 'fulfillment@example.com';
else if (eregi('bill|account', $feedback))
Stoaddress = 'accounts@example.com';
if (eregi('bigcustomer\.com', Semail))
Stoaddress = 'bob@example.com';
Замена подстрок с помощью
регулярных выражений
Регулярные выражения можно использовать для поиска и замены подстрок так
же, как это делалось с помощью функции str_replace (). Для решения этой задачи
предназначены две функции: ereg_replace () и eregi_replace ().
Функция ereg_replace () имеет следующий прототип:
string ereg_replace(string pattern, string replacement, string search);
Эта функция ищет регулярное выражение pattern в строке search и заменяет его
строкой replacement.
Функция eregi_replace() идентична предыдущей, за исключением того, что она
не чувствительна к регистру.
Разделение строк с помощью
регулярных выражений
Еще одна полезная функция обработки регулярных выражений, split(). имеет
следующий прототип:
array splitistring pattern, string search [, int max]);
Эта функция разделяет строку search на подстроки в соответствии с регулярным
выражением pattern и возвращает подстроки в массиве. Целочисленный параметр
max ограничивает количество подстрок, которые могут быть помещены в массив.
Эта функция может оказаться полезной для разделения адресов электронной поч-
ты, имен доменов и дат. Например:
Saddress = 'username@example.com';
$arr = split ('\.|@', Saddress);
while (list($key, Svalue) = each ($arr))
echo '<br />'.Svalue;
152
Часть I. Использование PHP
Приведенный выше фрагмент кода делит адрес электронной почты на три компо-
нента и выводит каждый из них в отдельной строке.
Сравнение функций обработки строк
и регулярных выражений
В общем случае функции регулярных выражений выполняются менее эффектив-
но, чем строковые функции с аналогичными возможностями. Если стоящая перед
вами задача достаточно проста, чтобы можно было использовать строковые функ-
ции, им и следует отдать предпочтение. Это может быть не так для задач, которые
решаются с помощью множества строковых функций, однако единственного регу-
лярного выражения.
Дополнительные источники информации
В РНР реализовано множество функций обработки строк. В этой главе мы рас-
смотрели наиболее полезные из них, но если у вас возникнет необходимость в каких-
то особенных функциях данного класса (например, для перевода символов в кирил-
лицу), просмотрите онлайновое руководство по РНР; возможно, вам удастся обнару-
жить PHP-функцию, которая вас устроит.
Регулярным выражениям посвящено огромное количество публикаций. Если вы
работаете в системе LTNIX, знакомство с ними можно начать с man-страницы для
функции regexp. Кроме того, несколько обширных статей находятся на сайтах
devshed.com и phpbuilder.com.
На Web-сайте Zend можно ознакомиться с более сложной и мощной функцией
проверки правильности адресов электронной почты, нежели та, которая была разра-
ботана в этой главе. Она называется Mail Vai () и доступна по следующему адресу:
http://www.zend.сот/codex,php?id=88&single=l
На освоение регулярных выражений потребуется некоторое время — чем больше
примеров вы просмотрите и выполните, тем увереннее будете применять регулярные
выражения.
Что дальше
В следующей главе мы рассмотрим несколько способов использования РНР для
снижения затрат времени и усилий на программирование, а также для предотвраще-
ния избыточности за счет повторного использования кода.
Глава 4. Манипулирование строками и регулярные выражения
153
5
Многократное
использование кода
и создание функций
В этой главе подробно объясняется, как повторное использование кода способст-
вует созданию более совместимых, надежных, удобных в эксплуатации про-
грамм, требующих меньших затрат на их сопровождение. Мы покажем, как приме-
нять технологии построения модулей и повторного использования кода, начиная с
простых операторов require () и include (), обеспечивающих использование одного
и того же программного кода на нескольких страницах. Мы покажем, в чем эти функ-
ции превосходят серверные включения. Предложенные примеры продемонстрируют
использование включаемых файлов для обеспечения непротиворечивого внешнего
вида всего сайта.
Мы поясним, как писать и вызывать ваши собственные функции на примере
функций генерации страниц и форм.
В этой главе, помимо прочих, рассматриваются следующие темы:
Многократное использование кода.
Использование операторов require () и include ().
Начальные сведения о функциях.
Определение функций.
Параметры функций.
Область действия.
Возвращаемые значения.
Передача по ссылке и передача по значению.
Реализация рекурсии.
Многократное использование кода
Одной из целей, которой обычно ставят перед собой разработчики программного
обеспечения, является повторное использование существующего программного кода
вместо написания нового. Это обусловлено отнюдь не тем, что разработчики про-
граммного обеспечения отличаются какой-то особенной ленью. Многократное ис-
пользование существующего кода снижает стоимость, повышает надежность и со-
вместимость программ. В идеале новый проект создается путем объединения су-
ществующих пригодных для многократного использования компонентов при
минимальном объеме разработки с нуля.
Стоимость
На протяжении эффективного срока службы какого-либо фрагмента программно-
го обеспечения поддержка, модификация, тестирование и документирование требу-
ют гораздо больших затрат времени, нежели его создание. При написании коммерче-
ского кода следует пытаться ограничить количество строк, характерных для
конкретной организации. Один из наиболее практичных способов достижения этой
цели предполагает повторное использование существующего кода вместо того, чтобы
для решения новой задачи писать слегка отличающуюся версию того же кода. Мень-
ший объем кода означает меньшие финансовые затраты. Если программное обеспе-
чение, которое отвечает требованиям нового проекта, уже существует, следует ис-
пользовать именно его. Стоимость приобретения существующего программного
обеспечения почти всегда меньше стоимости разработки эквивалентного продукта. С
другой стороны, к использованию существующего программного обеспечения, кото-
рое почти соответствует предъявляемым требованиям, следует подходить с особой
осторожностью. Иногда модификация существующего кода может оказаться труднее
создания нового.
Надежность
Если модуль кода уже используется где-то в организации, то, скорее всего, он был
тщательно протестирован. Даже если модуль кода состоит всего лишь из нескольких
строк, при его изменении существует вероятность пропустить что-то, что было пре-
дусмотрено автором первоначального варианта кода или было добавлено в этот код
после выявления недостатков в процессе тестирования. Как правило, существующий,
проверенный на практике код более надежен, чем новый, “незрелый”.
Единообразие
Внешние интерфейсы системы, включая пользовательские интерфейсы и интер-
фейсы с другими системами, должны быть совместимыми и непротиворечивыми.
Создание нового кода, который согласован с остальными частями системы, требует
целеустремленности и продуманных усилий. Если вы хотите воспользоваться кодом,
с которым взаимодействует другая часть системы, единообразие функционирования
должно обеспечиваться автоматически.
Но наиболее важное из всех этих преимуществ состоит в том, что повторное ис-
пользование кода означает меньший объем работы для вас как для разработчика, ра-
зумеется. при условии, что исходный код имеет модульную структуру и хорошо напи-
сан. В ходе работы над программами старайтесь распознать те разделы кода,
которыми можно будет пользоваться в будущем.
Глава 5. Многократное использование кода и создание функций
155
Использование операторов
require () и include ()
РНР предоставляет в распоряжение программиста два простых, но очень полез-
ных оператора, которые обеспечивают повторное использование любого типа кода.
Посредством операторов require () и include () можно загрузить файл в РНР-
сценарий. Файл может содержать все, что вы обычно включаете в сценарий, в том
числе PHP-операторы, текст, HTML-дескрипторы, PHP-функции или РНР-классы.
Эти операторы работают аналогично серверным включениям (server-side
include — SSI), которые поддерживаются многими Web-серверами, а также операто-
рами #include в языке С и C++.
require()
В файле с именем reusable. php хранится следующий код:
<?php
echo 'А это очень простой РНР-оператор.<Ьг />';
А в файле с именем main. php содержится такой код:
<?php
echo 'Это главный файл.<Ьг />';
require! 'reusable.php' );
echo 'Сейчас сценарий должен завершиться.<br />';
7>
Когда вы загружаете файл reusable .php, вас, должно быть, не удивляет, что в ок-
не браузера отображается текст “А это очень простой РНР-оператор”. Однако при
загрузке файла main. php происходит нечто более интересное. Вывод этого сценария
показан на рис. 5.1.
MoziBa {Build Ю: 2004112^06}
кШЕЖшВННММг
Файл Правка Вид Пережд Закладки Инструменты Окно Сгравка Debug Qa
з) » ,-й » Ж
Назад Вперед Обновить
jhttp ://iocalhosVphpmysql3e/chapter05/main php _*J ^„Лоиск!
• Это главный файл
А это очень простой РНР-оператор
: Сейчас сценарий должен завершиться
М Готово
Рис. 5.1. Вывод, генерируемый файлом main.php, отображает
результат выполнения оператора require ()
Чтобы можно было использовать оператор require (), необходим файл. В преды-
дущем примере использовался файл reusable.php. При выполнении этого сценария
оператор require ()
require! 'reusable.php' );
156
Часть I. Использование PHP
заменяется содержимым запрошенного файла, после чего сценарий выполняется.
Это означает, что загруженный файл main. php выполняется так, как если бы сцена-
рий имел следующий вид:
<?php
echo 'Это главный файл.сЬг />';
echo 'А это очень простой РНР-оператор.<Ьг />';
echo 'Сейчас сценарий должен завершиться.<Ьг />';
?>
При использовании оператора require () следует обратить внимание на различия
в обработке расширений имен файлов и РНР-дескрипторов.
Расширения имен файлов и оператор require ()
РНР игнорирует расширение имени запрашиваемого файла. Это означает, что
файл можно называть как угодно, если только вы не собираетесь вызывать его непо-
средственно. При использовании оператора require () для загрузки файла, он фак-
тически становится частью PHP-файла, и будет соответствующим образом выпол-
няться.
Обычно PHP-операторы не должны выполняться, если они находятся в файле с
именем, например, page.html. Как правило, РНР вызывается только для синтаксиче-
ского разбора файлов с определенными расширениями, такими как, например, .php.
(Это можно изменить через файл конфигурации Web-сервера.) Однако если загру-
зить файл page.html с помощью оператора required, любые хранящиеся внутри
него PHP-операторы будут обработаны. Следовательно, для включаемых файлов
можно выбирать любые подходящие расширения, однако имеет смысл придержи-
ваться определенных соглашений и использовать расширения, несущие тот или иной
смысл, например, . inc.
При этом следует иметь в виду, что если файлы, имеющие расширение . inc или
какое-то другое нестандартное расширение, сохраняются в дереве Web-документов, и
пользователи непосредственно загружают их в браузеры, они смогут просмотреть
содержащийся в них код в виде простого текста, включая любые находящиеся там
пароли. Поэтому важно либо хранить включаемые файлы вне дерева документов, ли-
бо использовать стандартные расширения.
PHP-дескрипторы и оператор require ()
В рассматриваемом примере повторно используемый файл (reusable.php) состо-
ит из следующей записи:
<?php
echo 'А это очень простей РНР-оператор.<Ьг />';
PHP-код размещен внутри РНР-дескрипторов. Это нужно делать, если вы хотите,
чтобы PHP-код внутри запрошенного вами файла обрабатывался именно как РНР-
код. Если не открывать PHP-дескриптор, РНР будет рассматривать этот код просто
как текст или HTML-код и выполнять его не будет.
Глава 5. Многократное использование кода и создание функций
157
Использование оператора require ()
для шаблонов Web-сайта
Если внешний вид Web-страниц на сайте вашей компании является единообраз-
ным, вы можете воспользоваться РНР для добавления в страницы шаблонов и стан-
дартных элементов с помощью оператора require ().
Например, Web-сайт вымышленной компании TLA Consulting содержит не-
сколько страниц, и все они выглядят так. как показано на рис. 5.2. Когда возникает
потребность в новой странице, разработчик может открыть существующую страни-
цу, вырезать существующий текст из файла, ввести в него новый текст и сохранить
полученный файл под новым именем.
Рис. 5.2. Все Web-страницы сайта компании TLA Consulting име-
ет стандартный внешний вид
Рассмотрим следующий сценарий развития событий. Web-сайт уже существует в
течение некоторого времени, и теперь в нем содержатся десятки, сотни или, воз-
можно, тысячи страниц, причем все они выдержаны в едином стиле. Принято ре-
шение частично изменить стандартный вид — изменение может быть совсем незна-
чительным, например, включение адреса электронной почты в нижний колонтитул
или добавление одной новой записи в меню навигации по страницам. Как вам по-
нравится перспектива вносить изменения, пусть даже и небольшие, в десятки, сот-
ни или даже тысячи страниц?
Непосредственное многократное использование разделов HTML, общих для
всех страниц, представляет собой значительно более рациональный подход, неже-
ли вырезание и вставка, выполняемая в десятках, сотнях или тысячах страниц. Ис-
ходный код домашней страницы (home.html), показанной на рис. 5.2, приведен в
листинге 5.1.
158
Часть I. Использование РНР
Листинг 5.1. home.html — HTML-код, создающий домашнюю
страницу компании TLA Consulting
<html>
<head>
<title>TLA Consulting Pty Ltd</title>
<style type="text/css">
hl {color:white; font-size:24pt; text-align:center;
font-family:arial,sans-serif}
.menu {color:white; font-size:12pt; text-align:center;
font-family:arial,sans-serif; font-weight:bold}
td {background:black}
p {color:black; font-size:12pt; text-align:justify;
font-family:arial,sans-serif}
p.foot {color:white; font-size:9pt; text-align:center;
font-family:arial,sans-serif; font-weight:bold}
a:link,a:visited,a:active {color:white}
</style>
</head>
<body>
<!-- верхний колонтитул страницы -->
<table width="i00%" cellpadding ="12" cellspacing ="0" border ="0">
<tr bgcolor ="black">
<td align ="left"><img src ="logo.gif" а11=''Логотип TLA" height="70”
width="70"><'td>
<td>
<hl>TLA Consulting</hl>
</td>
<td align ="right"ximg src ="logo.gif" а11="Логотип TLA" height="70”
width="70’>< td>
</tr>
</table>
<!-- меню -->
<table width="100%" bgcolor= "white" cellpadding="4" cellspacing="4">
<tr >
<td width="25%">
<img src ="s-logo.gif" alt="" height="20" width="20">
<span class="menu">Домой</spanx/td>
<td width ="25%">
<img src ="s-logo.gif" alt="" height="20" width="20">
<span class= "menu">KoHTaKT</spanx/td>
<td width ="25%">
<img src ="s-logo.gif" alt="" height="20" width="20”>
< span с1as s ="menu">Услуги<Ispanx/1d>
<td width ="25%">
<img src ="s-logo.gif" alt="" height="20" width="20">
<span class="menu">KapTa сайта</spanx/td>
</tr>
</table>
<!-- содержимое страницы -->
<р>Добро пожаловать на сайт компании TLA Consulting.
Пожалуйста, уделите некоторое время на знакомство с нами.</р>
<р>Мы специализируемся на обслуживании ваших деловых нужд
и надеемся скоро вас увидеть снова.</р>
Глава 5. Многократное использование кода и создание функций
159
<!-- нижний колонтитул страницы
<table width="100%" bgcolor="black" cellpadding=* 12 * bcrder=*G*>
<tr>
<td>
<p class="foot">© TLA Consulting Pty Ltd.< p>
<p с1азз="1оо!">Пожалуйста, просмотрите <a href="legal.php*>нагу страницу
с официальной информацией</а>< р>
</td>
</tr>
</table>
</body>
</html>
Как видно из листинга 5.1, в этом файле имеется несколько отдельных разделов
кода. HTML-заголовок содержит CSS-определения (Cascading Style Sheet -- каскадные
таблицы стилей), применяемые страницей. Раздел, озаглавленный как "верхний ко-
лонтитул страницы”, отображает название компании и ее логотип, раздет "меню”
создает линейку навигационного меню, а раздел “содержимое страницы" представля-
ет текст, уникальный для данной страницы. Под ними расположен раздел "нижний
колонтитул страницы”. Мы можем успешно разделить этот файл на части и присво-
ить соответствующим частям имена header, inc, home, php и footer, inc. Файлы
header. inc и footer. inc содержат код, который будет повторно использоваться на
других страницах.
Файл home.php служит заменой для файла home.html и содержит \никальное со-
держимое страницы и два оператора require (), как показано в листинге 5.2.
Листинг 5.2. home.php — PHP-код, создающий домашнюю страницу
сайта компании TLA Consulting
<?php
require('header.inc') ;
<!— содержимое страницы -->
<р>Добро пожаловать на сайт компании TLA Consulting.
Пожалуйста, уделите некоторое время на знакомство с нами.< р>
<р>Мы специализируемся на обслуживании ваших деловых нужд
и надеемся скоро вас увидеть снова.</р>
<?php
require('footer.inc');
?>
Операторы require)) в файле home, php загружают файлы header. inc и
footer.inc.
Как уже отмечалось ранее, имена, присвоенные этим файлам, не влияют на способ
их обработки при вызове с помощью оператора require (). Общепринято, но совсем
не обязательно, называть частичные файлы, которые предназначены для включения
в другие файлы, например, something, inc (в данном случае "inc" означает включае-
мый (“include”) файл). Кроме того, общепринято и имеет смысл помещать включае-
мые файлы в каталог, который доступен сценарию, но не позволяет индивидуально
загружать включаемые файлы через Web-сервер — то есть, каталог расположен вне
дерева Web-документов. Это препятствует самостоятельной загрузке включаемых
160
Часть I. Использование РНР
файлов, что чревато либо появлением различного рода ошибок, когда расширением
файла является . php, а сам файл содержит только часть страницы или сценария, либо
возможностью доступа к исходному коду со стороны пользователей в случае указания
другого расширения.
Файл header.inc содержит CSS-определения, используемые этой страницей, и
таблицы, которые отображают название компании и навигационное меню (см. лис-
тинг 5.3).
Листинг 5.3. header. inc — повторно используемый верхний колонтитул
для всех Web-страниц сайта компании TLA Consulting
ehtml>
<head>
etitle>TLA Consulting Pty Ltd</title>
<style type="text/css">
hl {color:white; font-size:24pt; text-align:center;
font-family:arial,sans-serif)
.menu {color:white; font-size:12pt; text-align:center;
font-family:arial,sans-serif; font-weight:bold)
td {background:black)
p {color:black; font-size:12pt; text-align:justify;
font-family:arial,sans-serif)
p.foot {color:white; font-size:9pt; text-align:center;
font-family:arial.sans-serif; font-weight:bold)
a:link,a:visited,a:active (color:white)
</style>
</head>
<body>
<!— верхний колонтитул страницы -->
<table width="100%" cellpadding ="12" cellspacing = "0" border =”0”>
<tr bgcolor ="black">
<td align = ”left”ximg src ="logo.gif" а1Г="Логотип TLA" height="70"
width="7 0“></td>
<td>
ehl>TLA Consultinge/hl>
</td>
<td align ="right"ximg src =“logo.gif" alt=”JIoroTnn TLA" height="70”
width="7 0"></td>
</tr>
</table>
<!— меню —>
etable width="100%" bgcolor= "white" cellpadding=“4“ cellspacing="4">
etr >
<td width=”25%">
<img src ="s-logo.gif" alt="" height="20" width="20">
<span class="menu“>floMon</spanx/td>
<td width ="25%">
<img src ="s-logo.gif" alt="" height=“20” width="20">
<span class= “menu">Контакте/spanx/td>
<td width ="25%'>
<img src ="s-logo.gif” alt=“" height="20" width="20">
espan class="menu">Услугие/spanx/td>
Глава 5. Многократное использование кода и создание функций
161
<td width ="25%">
<img src =”s-logo.gif" alt="" height="20” width=”21">
<span class="menu">KapTa cafjia</spanx/td>
</tr>
</table>
Файл footer, inc содержит таблицу, которая отображает нижний колонтитул в
нижней части каждой страницы. Этот файл показан в листинге 5.4.
Листинг 5.4. footer. inc — повторно используемый нижний колонтитул для всех
Web-страниц сайта компании TLA Consulting
<!— нижний колонтитул страницы -->
<table width ="100%” bgcolor ="black” cellpadding= "12” border="0">
<tr>
<td>
<p class="foot">© TLA Consulting Pty Ltd.</p>
<p с1азз="£оог">Пожалуйста, просмотрите <a href="legal.php”>нашу страницу
с официальной информацией*:/а>< р>
</td>
</tr>
</table>
</body>
</html>
Такой подход позволяет очень легко получить единообразно выглядящий Web-
сайт, и новую страницу в этом же стиле можно создать, набрав что-то наподобие:
<?php require)'header.inc’); ?>
Здесь находится содержимое новой страницы
<?php require(’footer.inc'); ?>
Самое главное, даже после того, как мы создали многие страницы, использующие
этот верхний и нижний колонтитулы, можно легко изменить сами файлы верхнего и
нижнего колонтитула. Независимо от того, вносятся ли незначительные изменения в
тексте, или полностью модифицируется внешний вид сайта, изменение потребуется
внести только один раз. Не нужно изменять каждую страницу сайта по отдельности,
поскольку каждая страница загружает файлы верхнего и нижнего колонтитулов.
В приведенном выше примере, в теле страницы, в ее верхнем и нижнем колонти-
тулах используется только простой HTML-код. Это вовсе не обязательно. Внутри этих
файлов можно использовать PHP-операторы для динамической генерации частей
страницы.
Если требуется, чтобы файл трактовался как простой текст или HTML, при этом
PHP-код не выполнялся, следует использовать readfilef). Эта функция выводит со-
держимое файла без какого-либо синтаксического разбора. Подобный подход может
существенно повысить безопасность при обработке текста, введенного пользователем.
Использование оператора include ()
Операторы require()n include)) почти ни в чем не отличаются друг от друга.
Единственное их различие состоит в том, что в случае их неудачного выполнения
162
Часть I. Использование РНР
конструкция require () выдает сообщение о неисправимой ошибке, в то время как
конструкция include () выдает всего лишь предупреждение.
Использование операторов require_once ()
и include once()
Существуют две вариации операторов require)) и include)), называемые, соот-
ветственно, require_once () и include_once(). Назначение этих конструкций, каки
должно быть понятно, состоит гарантированном однократном включении необходи-
мого файла. Для предыдущих примеров с верхним и нижним колонтитулами их
функциональность не особенно полезна.
Функциональность операторов require_once() и include_once() становится в
действительности полезной в случае включения библиотек функций. Использование
этих конструкций защищает от случайного повторного включения одной и той же
библиотеки, что приведет к переопределению функций и вызовет ошибку.
Использование конфигурационных параметров
auto _jprepend_fileи auto_append_file
Если вы не хотите использовать оператор require)) или include)) для вклю-
чения верхнего и нижнего колонтитулов в каждую страницу, можно решить эту
задачу другим способом. Файл php. ini поддерживает две опции конфигурации:
auto_prepend_f ile и auto_append_f ile. Настроив эти опции так, чтобы они указы-
вали на файлы верхнего и нижнего колонтитулов, можно обеспечить загрузку этих
файлов, соответственно, перед и после каждой страницы. Файлы, включаемые с ис-
пользованием данных опций, ведут себя так, как будто они включены с помощью
include (); то есть, если файл не существует, выдается предупреждение.
В Windows настройки будут выглядеть примерно так:
auto_prepend_file = "С:/Apache/include/header.inc"
auto_append_file = “С:/Apache/include/footer.inc"
В UNIX они должны иметь следующий вид:
auto_prepend_file = "/home/username/include/header.inc"
auto_append_file = "/home/username/include/footer.inc"
В случае использования этих директив отпадает необходимость вводить операто-
ры include (), однако верхние и нижние колонтитулы будут выводиться на страницы
всегда.
Если вы работаете с Web-сервером Apache, то можете менять значения различных
опций конфигурации, подобных этим, применительно к отдельному каталогу. Чтобы
это стало возможно, сервер должен быть настроен таким образом, чтобы разрешать
замещать его главный файл (файлы) конфигурации. Чтобы установить автоматиче-
ское добавление колонтитулов перед и после какого-либо файла для конкретного ка-
талога, создайте в нем файл с именем .htaccess. Этот файл должен содержать сле-
дующие две строки:
php_value auto_prepend_file "/home/username/include/header.inc"
phpvalue auto_append_file "/home/username/include/footer.inc"
Глава 5. Многократное использование кода и создание функций
163
Обратите внимание, что синтаксис несколько отличается от синтаксиса этого же
параметра в файле php. ini, в частности, знак равно отсутствует. Ряд других настроек
конфигурации в файле php. ini также можно изменять аналогичным способом.
Установка опций в файле .htaccess, а не в файле php.ini или в файле конфигу-
рации Web-сервера обеспечивает очень большую степень свободы. Вы можете менять
настройки совместно используемого компьютера, которые затрагивают только ваши
каталоги. При этом не нужно перезапускать Web-сервер, а также иметь права админи-
стратора. Недостаток метода, предусматривающего использование .htaccess, состо-
ит в том, что эти файлы считываются и анализируются при каждом запросе какого-
либо файла из данного каталога, а не один раз при начальном запуске компьютера,
что приводит к снижению производительности.
Использование функций в РНР
Функции существуют во многих языках программирования. Они служат для выде-
ления кода, который выполняет отдельную, хорошо определенную задачу. Это упро-
щает чтение кода и позволяет его использовать всякий раз. когда нужно выполнить
эту задачу.
Под функцией понимают независимый модуль кода, который устанавливает ин-
терфейс вызова, выполняет определенную задачу и при необходимости возвращает
результат.
Мы уже сталкивались с некоторыми функциями. В предшествующих главах мы по-
стоянно обращались к ряду функций, встроенных в РНР. Мы также сами написали
несколько простых функций, но при этом не особенно вникали в детали. В этом раз-
деле вызов и построение функций описываются более подробно.
Вызов функций
Следующая строка есть простейшее обращение к функции:
function_name();
Она вызывает функцию с именем function_name, которая не требует параметров.
Эта строка кода игнорирует любое значение, которое может возвратить эта функция.
Множество функций вызываются именно таким образом. Функция phpinf о () час-
то оказывается полезной во время тестирования, поскольку она показывает, какая
версия РНР установлена, сообщает информацию о РНР, параметры установки Web-
сервера, а также значения различных переменных РНР и сервера. Эта функция не
принимает никаких параметров, а мы в общем случае игнорируем значение, которое
она возвращает; поэтому вызов phpinf о () будет иметь следующий вид:
phpinfо();
Большинство функций требует передачи им одного или большего числа парамет-
ров, то есть, информации, передаваемой в функцию во время ее вызова и влияющей
на результат ее выполнения. Мы передаем ей параметры, помещая данные или имя
переменной, которая содержит данные, в круглые скобки, следующие за именем
функции. Обращение к функции с параметром принимает примерно такой вид:
function_name(’параметр1);
164
Часть I. Использование РНР
В этом случае использованным параметром является строка, содержащая слово
параметр; следующие вызовы также являются обращениями к функции, в зависимо-
сти от ожидаемого типа параметра:
func t ion_name(2);
function_name(7.993);
function_name($variable);
В последней строке переменная $variable может быть PHP-переменной любого
типа, в том числе и массивом.
Параметр может быть данными любого типа, но конкретные функции обычно
требуют передачи конкретных типов данных.
Количество принимаемых функцией параметров, что каждый из них собою пред-
ставляет, и какой тип данных он должен иметь, можно выяснить из прототипа {proto-
type) функции. При описании функции часто приводится ее прототип.
Вот, например, как выглядит прототип функции fopen ():
resource fopen( string filename, string mode
[, bool use_include_path[, resource zcontext ]] );
Прототип представляет собой описание функции, и очень важно, чтобы вы умели
правильно интерпретировать его спецификации. В данном случае слово resource
перед именем функции указывает, что эта функция возвращает некоторый ресурс
(здесь дескриптор открытого файла). Параметры функции заключаются в круглые
скобки. В случае функции fopen () в прототипе указаны четыре параметра. Пара-
метры filename (имя_файла) и mode (режим) являются строками, параметр
use_include_path — булевским значением, а параметр zcontext — ресурсом. Квад-
ратные скобки вокруг use_include_path и zcontext показывают, что эти параметры
являются необязательными. Для необязательных параметров можно передавать зна-
чения, либо их можно игнорировать; в таких случаях используется значение, опреде-
ленное по умолчанию. Однако следует отметить, что если функция принимает более
одного необязательного параметра, опускать можно только самый правый из них.
Например, в случае fopen!) опустить можно либо zcontext, либо use_include_path
и zcontext, но нельзя оставить zcontext и опустить use_include_path.
После ознакомления с прототипом этой функции становится понятно, что в при-
веденном ниже фрагменте кода находится допустимый вызов fopen ():
$name = 'myfile.txt';
$openmode = ' г';
$fp = fopen($name, Sopenmode)
Этот код вызывает функцию с именем fopen (). Возвращаемое функцией значение
будет сохранено в переменной $fp. В функцию передается переменная $name, кото-
рая содержит строку, представляющую открываемый файл, и переменная Sopenmode,
которая содержит строку, указывающую режим для этого файла. Пока мы не переда-
ем функции необязательные третий и четвертый параметры.
Вызов неопределенной функции
При попытке вызвать несуществующую функцию вы получите сообщение об
ошибке, как показано на рис. 5.3.
Глава 5. Многократное использование кода и создание функций
165
Рис. 5.3. Это сообщение об ошибке является результатом вызо-
ва несуществующей функции
Как правило, выводимые РНР сообщения об ошибках приносят большую пользу.
Такое сообщение точно указывает имя и строку сценария, где была допущена ошибка,
а также имя функции, которую вы пытались вызвать. Эта информация должна суще-
ственно упростить поиск и исправление ошибки.
Получив сообщение об ошибке, вы должны проверить два момента:
1. Правильно ли указано имя функции?
2. Существует ли указанная в сообщении функция в используемой версии РНР?
Не всегда легко запомнить, как правильно пишется название функции. Например,
некоторые имена функций, состоящие из двух слов, содержат символ подчеркива-
ния между словами, а некоторые — нет. Так, в имени функции stripslashes () два
слова слиты в одно, в то время как в имени функции strip_tags () они разделены
символом подчеркивания. Неправильный ввод имени функции в вызове приводит к
ошибке (см. рис. 5.3).
Некоторые из использованных в книге функций отсутствуют в версии РНР4, по-
скольку мы предполагаем, что применяется, по меньшей мере, версия РНР5. В каж-
дой новой версии появляются новые функции, и если вы работаете с одной из ран-
них версий РНР, то дополнительные функциональные возможности и более высокая
производительность, характерные для новых версий, должны подтолкнуть к модер-
низации программного обеспечения. Если вы хотите выяснить, когда появилась та
или иная функция, вы можете навести соответствующие справки в онлайновом руково-
дстве. Попытка вызова функции, которая не объявлена в используемой вами версии,
приведет к появлению сообщения об ошибке, подобного показанному на рис. 5.3.
Еще одна причина получения упомянутого выше сообщения об ошибке связана с
тем, что вызываемая функция является частью PHP-расширения, которое не загру-
жено. Например, если вы попытаетесь воспользоваться функциями из библиотеки
манипулирования изображениями gd, а она не булла установлена, вы увидите данное
сообщение об ошибке.
Регистр символов и имена функции
Обратите внимание, что имена функций не чувствительны к регистру, поэтому
любое из обращений function_name (), Function_Name () или FUNCTION_NAME () яв-
ляется допустимым и приводит к одному и тому же результату. Прописные буквы
можно использовать в имени функции любым образом, который, по вашему мнению,
облегчает чтение, но при этом все же следует стремиться к какому-то единообразию.
166
Часть I. Использование РНР
В настоящей книге и в большей части документов по РНР принято применять строч-
ные буквы.
Важно отметить, что имена функций ведут себя иначе, чем имена переменных.
Имена переменных чувствительны к регистру, и поэтому $Name и $name — это разные
переменные, тогда как Name () и name () — одна и та же функция.
Для чего нужно определять
собственные функции?
В предыдущих главах вы ознакомились со многими примерами использования не-
которых встроенных функций РНР. Однако реальная сила языка программирования
связана с возможностью создания собственных функций.
Функции, встроенные в РНР, позволяют взаимодействовать с файлами, использо-
вать базы данных, создавать графические изображения и подключаться к другим сер-
верам. Однако вам придется столкнуться со многими случаями, когда придется вы-
полнять действия, не предусмотренные создателями языка.
К счастью, рамки программиста не ограничиваются встроенными функциями, по-
скольку для выполнения стоящих перед ним задач можно создавать свои собственные
функции. По-видимому, создаваемый код будет представлять собой некоторую смесь
существующих функций и специализированной логики выполнения той или иной
задачи. Если вы пишете блок кода решения некоторой конкретной задачи, который,
скорее всего, придется многократно использовать в нескольких местах сценария или
даже в нескольких сценариях, имеет смысл организовать этот блок как функцию.
Объявление функции позволяет использовать созданный код так же, как и встро-
енные функции. Вы просто вызываете функцию и передаете ее все необходимые па-
раметры. Это означает, что одну и ту же функцию вы можете многократно вызывать
и использовать в своем сценарии.
Базовая структура функции
Объявление функции создает, или объявляет (declare), новую функцию. Объявле-
ние начинается с ключевого слова function, оно присваивает функции имя, задает
необходимые параметры и содержит код, который выполняется при каждом вызове
функции.
Объявление простейшей функции имеет примерно такой вид:
function my_function()
{
echo 'Вызвана моя функция';
)
Это объявление функции начинается с ключевого слова function, которое сооб-
щает человеку и программе синтаксического анализа РНР, что далее следует описа-
ние определяемой пользователем функции. В данном случае именем функции являет-
ся my_function. Новую функцию можно вызвать с помощью следующего оператора:
my_function();
Глава 5. Многократное использование кода и создание функций
167
Как вы уже, наверное, догадались, вызов этой функции приведет к отображению
текста Вызвана моя функция в окне браузера.
Встроенные функции доступны для всех PHP-сценариев, но если вы объявляете
свои собственные функции, то они доступны только для того сценария (сценариев), в
которых они были объявлены. Имеет смысл завести отдельный файл и поместить в
него все обычно используемые функции. Тогда в каждый из своих сценариев вы мо-
жете с помощью оператора require () получить доступ к этим функциям.
Внутри функции код, который выполняет требуемую задачу, заключается в фигур-
ные скобки. Внутри скобок вы можете поместить любые конструкции, допустимые в
любом месте PHP-сценария, в том числе вызовы функций, объявления новых пере-
менных или функций, операторы require О или include О и обычный HTML-текст.
Если внутри функции нужно выйти из среды РНР и ввести обычный HTML-текст, это
делается так же, как в любом другом месте сценария, то есть устанавливается закры-
вающий PHP-дескриптор, за которым помещается HTML-текст. Ниже показан допус-
тимый вариант ранее приведенного примера, который генерирует такой же вывод:
<?php
function my_function))
(
Вызвана моя функция
<?php
}
Обратите внимание на то, что PHP-код заключен между соответствующими от-
крывающим и закрывающим PHP-дескрипторами. На протяжении этой книги в
большинстве коротких фрагментов кода эти дескрипторы опускаются. Здесь же они
показаны, так как они необходимы внутри примера, а также выше и ниже его.
Именование функций
Самое важное при выборе имени функции заключается в том, чтобы это имя было
кратким и несло соответствующую смысловую нагрузку'. Если функция создает верх-
ний колонтитул страницы, подходящим именем может быть pageheader () или
page_header().
На имена функций накладываются следующие ограничения:
Функция не может иметь то же имя. что у существующей функции.
Имя функции может содержать только буквы, цифры и символы подчеркивания.
Имя функции не может начинаться с цифры.
Многие языки программирования допускают многократное использование имен
функций. Это свойство называется перегрузкой функций (function overloading). Однако
РНР не поддерживает перегрузку функций, поэтому' функция не может иметь имя,
совпадающее с именем любой встроенной или существующей функции, определяе-
мой пользователем. Имейте в виду, что хотя любой PHP-сценарий распознает все
встроенные функции, тем не менее, определяемые пользователем функции сущест-
вуют только в тех сценариях, в которых они объявлены. Это означает, что имя функ-
168
Часть I. Использование РНР
ции можно повторно использовать в другом файле, но это может привести к путани-
це, поэтому подобных ситуаций следует избегать.
Следующие имена функций являются допустимыми:
name()
name2()
name_three()()
_namefour()
А эти имена недопустимы:
5name()
name-six()
fopen ()
(Последнее имя могло бы быть допустимым, если бы оно не было именем уже су-
ществующей функции.)
Обратите внимание, что хотя Sname не является допустимым именем функции,
следующий вызов функции:
$name();
может нормально работать, в зависимости от значения, хранящегося в $name. При-
чина состоит в том, что РНР берет значение переменной $name, ищет функцию с
именем, совпадающим с этим значением, и пытается к ней обратиться. Данный тип
функции носит название функции-переменной и часто оказывается весьма полезен.
Параметры
Чтобы иметь возможность выполнять свои задачи, большинство функций тре-
буют передачи в них одного и более параметров. Параметр позволяет передавать
данные в функцию. Ниже приведен пример функции, которая требует передачи в
нее одного параметра. Эта функция принимает одномерный массив и отображает
его в виде таблицы.
function create_tabie($data)
{
echo '<table border = !>';
reset($data); // Помните, что это делается для указания на начало
Svalue = current($data);
while ($value)
{
echo "<tr><td>$value</tdx/tr>\n" ;
$value = next($data);
}
echo '</table>';
}
Если вызвать функцию create_table () следующим образом:
$my_array = array('Строка один.','Строка два.','Строка три.');
create_table($my_array);
на экране отобразится вывод, показанный на рис. 5.4.
Глава 5. Многократное использование кода и создание функций
169
Рис. 5.4. Эта HTML-таблица получена в результате вызова функ-
ции create_table()
Параметр дает возможность передавать данные в функцию, которые были созда-
ны за ее пределами, — в данном случае, массив $data.
Как и в случае встроенных функций, определяемые пользователем функции могут
принимать несколько параметров и иметь необязательные параметры. Мы можем
усовершенствовать функцию create_table () различными способами, одним из них
могло бы быть предоставление пользователю возможности задавать параметры рам-
ки или другие атрибуты таблицы. Ниже приведена улучшенная версия этой функции.
Она мало чем отличается от предыдущей версии, тем не менее, она позволяет при
желании определить ширину рамки, расстояние между ячейками и способ заполне-
ния ячеек.
function create_table2( $data, Sborder = 1, Scellpadding = 4, Scellspacing = 4 )
{
echo "<table border=$border cellpadding = $cellpadding"
." cellspacing=$cellspacing>";
reset($data);
$value = current($data);
while ($value)
{
echo "<trxtd>$value</tdx/tr>\n" ;
$value = next($data);
}
echo '</tables-
}
Первый параметр функции create_table2 () по-прежнему обязателен. Следующие
три являются необязательными, поскольку для них определены значения по умолча-
нию. Вывод, очень похожий на показанный на рис. 5.4, получается в результате сле-
дующего вызова функции create_table2 ():
create_table2($my_array);
Если мы хотим, чтобы эти же данные отображались в более просторной форме,
эту функцию можно вызвать так:
create_table2($my_array, 3, 8, 8);
Необязательные параметры можно передавать не все, а лишь некоторые из них.
Параметры присваиваются слева направо.
170
Часть I. Использование РНР
Не следует забывать, что пропускать какой-либо необязательный параметр и по-
сле него задавать параметр, следующий за ним, не допускается. В данном примере,
если мы хотим задать значение napaMeipacellspacing, нам придется передать также и
значение параметра cellpadding. Это ограничение часто становится причиной оши-
бок программирования. Именно поэтому необязательные параметры помещаются в
конце любого списка параметров.
Следующий вызов функции:
create_table2($my_array, 3);
вполне допустим; он приводит к тому, что значение переменной $border устанавли-
вается равным 3, а значениям переменных $cellpadding и $cellspacing получают
соответствующие значения по умолчанию.
Существует возможность объявлять функции, которые принимают переменное ко-
личество параметров. Определить, сколько параметров передано, а также их значения,
можно посредством вспомогательных функций func_num_args (), func_get_arg () и
func_get_args().
Для примера рассмотрим следующую функцию:
function var_args()
{
echo "Количество параметров:
echo func_num_args();
echo '<br />';
Sargs = func_get_args();
foreach ($args as $arg)
echo $arg.'<br />';
}
Эта функция выводит количество переданных ей параметров вместе с их значе-
ниями. Функция func_num_args () возвращает количество переданных аргументов, а
функция func_get_args () — массив, содержащий эти аргументы. В качестве аль-
тернативы можно получать доступ к каждому аргументу с помощью функции
func_get_arg (), передавая ей номер требуемого аргумента. (Нумерация аргументов
начинается с нуля.)
Область действия
Вы, должно быть, уже обратили внимание на то, что при необходимости исполь-
зования переменных внутри затребованного или включаемого файла, мы просто
объявляем их в сценарии перед оператором require () или include (). Однако при
использовании функции эти переменные передаются в нее явно. Частично это связа-
но с тем, что не существует никакого механизма для явной передачи переменных в
затребованный или включаемый файл, а частично с тем, что область действия пере-
менных в функциях определяется иначе.
Область действия переменных задает границы, в рамках которых переменная ви-
дима и может использоваться. В различных языках программирования действуют
различные правила, устанавливающие область действия переменных. В РНР дейст-
вуют очень простые правила:
Глава 5. Многократное использование кода и создание функций
171
Переменные, которые объявлены внутри функции, действуют в области от
оператора, в котором они объявлены, до закрывающей скобки в конце функ-
ции. Эта область называется областью действия функции (Junction scope), а сами
переменные — локальными (local) переменными.
Переменные, которые объявлены за пределами функции, действуют в области
от оператора, в котором они объявлены, до конца файла, но не внутри функций.
Эта область называется глобальной областью действия (global scope), а такие пере-
менные — глобальными (global) переменными.
Специальные суперглобальные переменные видны как внутри функции, так и
за ее пределами. (Дополнительную информацию по таким переменным можно
найти в главе 1.)
Использование операторов require () и include О не влияет на область дейст-
вия переменных. Если оператор используется внутри функции, его областью
действия является область действия функции. Если он используется за преде-
лами функции, его областью действия является глобальная область.
Ключевое слово global может использоваться для указания вручную того, что
переменная, которая определена или используется внутри функции, должна
иметь глобальную область действия.
Переменные могут быть вручную удалены посредством вызова функции
unset ($имя_леременной). Если переменная удалена, она теряет свою область
действия.
Приведенные далее примеры помогут внести ясность в данные выше определения.
Следующий код не генерирует никакого вывода. В нем переменная $var объявля-
ется внутри функции f п (). Поскольку эта переменная объявляется внутри функции,
она имеет область действия в рамках функции и существует в области от места ее
объявления до конца функции. При обращении к переменной $var за пределами
функции, создается новая переменная с именем $var. Эта новая переменная имеет
глобальную область действия и будет видимой до конца файла. К сожалению, если
единственным оператором, применяемым в отношении этой новой переменной
$var, будет echo, она никогда не будет иметь значения.
function fn()
{
$var = 1 значение';
}
fn() ;
echo $var;
Далее следует пример противоположного свойства. В нем объявляется перемен-
ная за пределами функции, а затем предпринимается попытка ее использовать ее
внутри функции.
function fn()
{
echo 'внутри функции, \$var = ’.$var.'<br />';
$var = 'значение 2';
echo 'внутри функции, \$var ='.$var.’<br />';
}
172
Часть I. Использование PHP
$var = 'значение 1";
fn() ;
echo 'вне функции, \$var ='.$var.'<br />';
Этот код сгенерирует следующий вывод:
внутри функции, $var =
внутри функции, $var = значение 2
вне функции, $var = значение 1
Функции не выполняются до тех пор, пока они не будут вызваны, поэтому первым
выполняемым оператором является $var = 1 значение 1' ;. Он создает переменную
$var, имеющую глобальную область действия и содержимое ' значение 11. Следую-
щий выполняемый оператор — вызов функции f п (). Строки внутри оператора вы-
полняются по очереди. Первая строка в функции обращается к переменной $var. Ко-
гда эта строка выполняется, она не может видеть предшествующую созданную нами
переменную $var. поэтому она создает новую переменную, имеющую область дейст-
вия в рамках функции, и выводит ее. В результате создается первая строка вывода.
Следующая строка внутри функции устанавливает содержимое переменной $var
равным ' значение 2 ’. Поскольку действия выполняются внутри функции, эта строка
изменяет значение локальной переменной $var, а не глобальной. Вторая строка вы-
вода подтверждает выполнение этого изменения.
На этом выполнение функции завершается, поэтому выполняется заключительная
строка сценария. Этот оператор echo показывает, что значение глобальной перемен-
ной не изменилось.
Если вы хотите, чтобы переменная, созданная внутри функции, была глобальной,
можно воспользоваться ключевым словом global, как показано в следующем примере:
function £п()
{
global $var;
$var = 'значение'
echo 'внутри функции, \$var = '.$var.'<br />';
}
fn () ;
echo "вне функции, \$var = ”.$var.'<br />';
В этом примере переменная $var была явно объявлена как глобальная, следова-
тельно, после вызова функции переменная будет существовать и вне функции. Вывод
этого сценария будет иметь следующий вид:
внутри функции, $var = значение
вне функции, $var = значение
Обратите внимание, что переменная определена в области действия, начинаю-
щейся с того места, в котором выполняется строка global $var;. Функцию можно
было бы объявить выше или ниже того места, в котором она вызывается. (Также об-
ратите внимание, что область действия функции существенно отличается от области
действия переменной!). Место объявления функции фактически не играет роли —
важно лишь то, где функция вызывается и, следовательно, где выполняется содержа-
щийся внутри нее код.
Глава 5. Многократное использование кода и создание функций
173
Ключевое слово global можно указывать также в начале сценария при первом ис-
пользовании переменной, чтобы подчеркнуть, что областью ее действия должен
быть весь сценарий. Возможно, это наиболее распространенное использование клю-
чевого слова global.
Как видно из приведенных выше примеров, вполне допустимо повторно исполь-
зовать имя переменной внутри и вне функции без взаимного влияния друг на друга.
Однако в общем случае делать это не рекомендуется, поскольку, глубоко не вникнув в
код и не сопоставив области действия переменных, пользователи могут решить, что
эти на самом деле разные переменные являются одной и той же переменной.
Передача по ссылке и передача по значению
Если требуется создать функцию с именем increment (), позволяющую увеличи-
вать целочисленное значение на единицу, можно попытаться решить эту задачу сле-
дующим образом:
function increment($value, $amount = 1)
{
$value = $value + $amount;
1
Однако этот код оказывается бесполезным. В результате выполнения показанного
ниже теста выводится значение " 10".
$value = 10;
increment ($value);
echo $value;
Как видим, содержимое переменной $value не изменилось.
Такой результат есть следствие правил, регламентирующих область действия.
Представленный выше код создает переменную $value, которая содержит значение
10. Затем вызывается функция increment (). Переменная $value создается в функции
во время вызова функции. К значению этой переменной добавляется 1, поэтому
внутри функции значение $value равно 11 до тех пор, пока выполнение функции не
завершается и не осуществляется возврат в вызвавший ее код. В вызвавшем коде в
качестве переменной $value выступает другая переменная, определенная в глобаль-
ной области, а она как раз и остается неизменной.
Один из способов решения этой проблемы предполагает объявление переменной
$ value в функции в качестве глобальной, но это означает, что для использования
этой функции переменную, значение которой требуется увеличить, нужно назвать
$value. Более рациональным подходом было бы использование передачи по ссылке
(pass by reference).
Обычный способ вызова параметров функции называется передачей по значению
(pass by value). Когда вы передаете параметр, создается новая переменная, которая
содержит передаваемое значение. Она представляет собой копию исходной пере-
менной. Данное значение можно изменять как угодно, но при этом значение исход-
ной переменной вне функции остается неизменным.
Более рациональный подход предусматривает использование передачи по ссылке.
В этом случае при передаче параметра, вместо того чтобы создавать новое значение,
174
Часть I. Использование РНР
t
I
функция принимает ссылку на исходную переменную. Эта ссылка имеет имя пере-
менной, начинающееся со знака доллара ($), и может использоваться совершенно так
же, как и любая другая переменная. Различие состоит в том, что вместо того, чтобы
иметь собственное значение, она просто ссылается на исходную переменную. Любые
изменения, применяемые к ссылке, влияют также и на оригинал.
Передача используемого параметра по ссылке указывается путем помещения сим-
вола амперсанда (&) перед его именем в определении функции. Никакие изменения в
вызове функции не требуются.
Ранее приведенный пример функции increment () можно изменить, передав ей
один параметр по ссылке, после чего функция будет работать корректно.
function increment(&$value, $amount = 1)
{
$value = $value + $amount;
}
Теперь в нашем распоряжении работающая функция, и мы можем назвать пере-
менную, значение которой хотим увеличить, как нам заблагорассудится. Как уже упо-
миналось, использование одного и того же имени внутри и за пределами функции
может привести к путанице, поэтому переменной в основном сценарии мы присвоим
новое имя. Теперь следующий тестовый код будет выводить на экран значение 10 пе-
ред обращением к функции increment () и 11 — после него.
$а = 10;
echo $а.'<br />';
increment ($а);
echo $а '<br
Возврат из функции
Ключевое слово return останавливает выполнение функции. Когда выполнение
функции завершается либо из-за того, что все операторы выполнены, либо по при-
чине встречи ключевого слова return, управление возвращается оператору, следую-
щему за вызовом функции.
При вызове представленной ниже функции выполняется только первый оператор
echo.
function test_return()
{
echo 1 Этот оператор будет выполнен';
return;
echo 'Этот оператор никогда не будет выполнен1;
)
Очевидно, это не самый полезный способ использования оператора return. Обыч-
но возврат из функции требуется только в случае выполнения некоторого условия.
Условие возникновения ошибки — это наиболее распространенная причина при-
менения оператора return с целью преждевременного прекращения выполнения
функции. Если, например, вы написали функцию для определения большего из двух
чисел, возможно, вы захотите выйти из нее в случае отсутствия одного из чисел.
Глава 5. Многократное использование кода и создание функций
175
function larger) $x, $y )
i
if (1isset($x)||!isset($y))
{
echo 'Эта функция требует указания двух чисел1;
return;
}
if ($х>=$у)
echo $х;
else
echo $у;
echo '<br />'
}
Встроенная функция isset () сообщает, была ли создана переменная, и было ли
ей присвоено значение. Этот код генерирует сообщение об ошибке и выполняет воз-
врат, если значение какого-либо из параметров не установлено. Эта проверка выпол-
няется с помощью выражения ! isset (), означающего “НЕ isset))”, и следователь--
но, условный оператор i f можно прочесть как “если значение х не установлено или
если значение у не установлено”. Функция будет выполнять возврат, если любое из
этих условий истинно.
Если оператор return выполняется, то следующие за ним строки кода в функции
игнорируются. Выполнение программы вернется к точке, в которой функция была вы-
звана. Если оба параметра установлены, функция выведет на экран больший из них.
Вывод следующего кода:
$а = 1;
$Ь = 2.5;
$с = 1.9;
larger($а, $Ь);
larger($с, $а);
larger($d, $а);
будет иметь такой вид:
2.5
1.9
Эта функция требует указания двух чисел
Возврат значений из функции
Возврат из функции — не единственная причина применения оператора return.
Во многих функциях операторы return используются для обмена данными с вызы-
вающим их кодом. Функция larger () была бы более полезной, если бы вместо вывода
на экран результата сравнения она возвращала и само число. В этом случае вызвав-
ший функцию код мог бы принимать решение, нужно ли и когда именно нужно ото-
бражать или использовать это большее число. Эквивалентная встроенная функция
max () именно так и работает.
Функцию larger () можно определить следующим образом:
function larger ($х, $у)
{
176
Часть I. Использование РНР
if (!isset($x)||!isset($y))
return false;
else if ($x>=$y)
return $x;
else
return $y;
}
Эта функция возвращает большее из двух переданных ей значений. В случае
ошибки функция будет явно возвращать другое число. Если одно из чисел отсутству-
ет, функция larger () возвращает false. (При этом программисты, использующие
такой подход, должны иметь в виду, что тип возврата необходимо проверить с помо-
щью операции ===, дабы убедиться, что значение false не спутано с нулем.)
Для сравнения, встроенная функция max () ничего не возвращает, если обе пере-
менные не установлены, а если только одна переменная была установлена, функция
возвращает значение именно этой переменной.
Показанный ниже код:
$а = 1; $Ь = 2.5; $с = 1.9;
echo larger($a, $b).“<br />";
echo larger($c, $a)."<br />";
echo larger($d, $a)."<br />";
генерирует следующий вывод, поскольку переменная $d не существует, a false на
экран не выводится:
2.5
1.9
Часто функции, которые выполняют определенную задачу, и в то же время не воз-
вращают конкретных значений, возвращают true или false с тем, чтобы указать на ус-
пешное или неудачное выполнение этой задачи. Значения true и false могут быть, соот-
ветственно, представлены целыми значениями 1 или 0, хотя это значения другого типа.
Блоки кода
Группа операторов объявляется блоком путем заключения их в фигурные скобки.
Это не влияет на работу большей части разрабатываемого вами кода, но оказывает
специфическое влияние, в частности, на способ выполнения таких управляющих
структур, как циклы и условные операторы.
Следующие два очень похожих примера кода дают совершенно разные результаты.
Пример без блока кода
for($i = 0; $i < 3; $i++ )
echo 'Строка l<br />';
echo 'Строка 2<br />';
Пример с блоком кода
for($i = 0; $i < 3; $i++ )
{
echo 'Строка l<br />';
echo 'Строка 2<br />';
}
Глава 5. Многократное использование кода и создание функций
177
В обоих примерах цикл for повторяется три раза. В первом примере в цикле вы-
полняется только одна из строк, расположенная непосредственно под for. Вывод,
получаемый в результате выполнения этого примера, имеет следующий вид:
Строка 1
Строка 1
Строка 1
Строка 2
Во втором примере блок кода используется для группирования двух строк в еди-
ную структуру. Это означает, что обе строки выполняются в цикле for три раза. Вы-
вод, генерируемый этим примером, имеет вид:
Строка 1
Строка 2
Строка 1
Строка 2
Строка 1
Строка 2
Поскольку код в этих примерах показан с отступами, по-видимому, несложно сразу
же заметить различия между ними. Отступы в коде предназначены для того, чтобы
дать визуальное представление о том, какие строки затрагивает цикл for. Однако
следует помнить, что пробелы никак не влияют на обработку' РНР-кода.
В некоторых языках программирования блоки кода оказывают влияние на область
действия переменных, но в РНР это не так.
Реализация рекурсии
В РНР поддерживаются рекурсивные функции. Под рекурсивной функцией (recursive
function) понимается функция, которая вызывает саму себя. Эти функции особенно
полезны для перемещения по динамическим структурам данных, таким как связные
списки и деревья.
Очень немногие Web-приложения требуют столь сложных структур данных, по-
этому рекурсия используется достаточно редко. Во многих случаях рекурсия может
применяться вместо итерации, поскольку' оба эти подхода позволяют многократно
выполнять те или иные действия. Рекурсивные функции работают медленнее и ис-
пользуют больший объем памяти, нежели итерация, поэтому там, где это возможно,
следует отдавать предпочтение итерации.
Ради полноты изложения рассмотрим краткий пример, показанный в листинге 5.5.
Листинг 5.5. recursion.php — обращение строки с использованием рекурсии и итерации
function reverse_r($str)
{
if (strlen($str)>0)
reverse_r(substr($str, 1));
echo substr($str, 0, 1);
return;
)
178
Часть I. Использование PHP
function reverse_i($str)
for ($i=l; $i<=strlen($str); $i++)
{
echo substr($str, -$i, 1);
}
return;
В этом листинге представлены реализации двух функций. Обе они выводят строку'
в обратном порядке. Функция reverse__r () — рекурсивная, a reverse_i () — итераци-
онная.
Функция reverse_r () принимает строку в качестве параметра. При ее вызове она
будет вызывать саму себя, каждый раз передавая символы строки со второго до по-
следнего.
Например, если вызвать функцию следующим образом:
reverse_r(1 Пример’) ;
она вызовет себя шесть раз со следующими параметрами:
reverse_r(’ример');
reverse_r('имер');
reverse_r('мер');
reverse_r('ер');
reverse_r('р');
reverse_r(' ' ) ;
При каждом вызове самой себя функция создает новую копию кода функции в па-
мяти сервера, но с другим параметром. Внешне это выглядит так, словно в действи-
тельности каждый раз вызывается другая функция. Это предотвращает возникнове-
ние путаницы с экземплярами функции.
При каждом вызове проверяется длина переданной строки. По достижении конца
строки условие оказывается ложным (strlen () == 0). После этого последний экзем-
пляр функции (reverse_r (' ')) продолжит работу' и выполнит следующую строку ко-
да, то есть выведет на экран первый символ переданной строки — в данном случае
никакого вывода не будет, поскольку строка пуста.
Затем этот экземпляр функции возвращает управление экземпляру, который вы-
звал его, а именно, reverse_r ( ' т' ). Этот экземпляр выводит первый символ в своей
строке, ' р', и возвращает управление вызвавшему его экземпляру.
Упомянутый процесс — вывод символа и возврат к экземпляру функции, располо-
женному над ним в порядке вызова, — продолжается до тех пор, пока управление не
будет возвращено основной программе.
Рекурсивные решения весьма изящны и соответствуют методам математической
рекурсии. Однако в большинстве случаев лучше использовать итерационные реше-
ния. Код для реализации этого метода также показан в листинге 5.5. Обратите вни-
мание, что по длине он не превышает рекурсивный вариант (хотя при использовании
итерационных функций это не всегда так) и выполняет те же действия.
Основное различие между ними состоит в том, что рекурсивная функция создает
в памяти копии самой себя и, соответственно, приводит к непроизводительным
Глава 5. Многократное использование кода и создание функций
179
расходам ресурсов, которые обусловлены многократными вызовами одной и той же
функции.
Рекурсивное решение имеет смысл использовать, когда соответствующий код ока-
зывается гораздо короче и изящнее итеративной версии, но в данной области при-
менения подобное случается нечасто.
Хотя рекурсия выглядит более элегантно, программисты часто забывают опреде-
лить условие завершения рекурсии. Это означает, что функция будет продолжать вы-
зывать себя до тех пор, пока сервер не столкнется с нехваткой памяти или пока не
истечет максимальное время выполнения, в зависимости от того, что произойдет
раньше.
Дополнительные источники информации
Использование операторов include (), require (), function и return объясняется
также в онлайновом руководстве. Более подробно о таких понятиях, как рекурсия,
передача по значению/ссылке и область действия, которые применяются во многих
языках программирования, можно прочесть в учебниках по программированию.
Что дальше
Теперь, когда вы научились использовать включаемые и требуемые файлы и
функции для того, чтобы сделать свои программы более пригодными для сопровож-
дения и повторного использования, в следующей главе будет рассматриваться объ-
ектно-ориентированное программирование и его поддержка в РНР. Использование
объектов позволяет достичь целей, подобных концепциям, которые описаны в дан-
ной главе, но при этом достигаются еще большие преимущества, в особенности во
время разработки сложных проектов.
180
Часть I. Использование РНР
6
Объектно-ориентированное
программирование на РНР
В этой главе описаны основные понятия объектно-ориентированной разработки
и показано, как их можно внедрить с использованием РНР. В РНР 5.0 сущест-
венно улучшилась реализация концепций объектно-ориентированного программи-
рования, в результате чего классы и объекты стали более полезными.
В настоящий момент реализация объектно-ориентированного программирования в
РНР стала настолько полной, что этот язык по праву можно считать полноценным объ-
ектно-ориентированным языком. В настоящей главе рассматриваются как новые, так и
старые возможности, которыми вы успешно пользовались в предыдущих версиях.
В главе, помимо прочих, рассматриваются следующие темы:
Концепции объектно-ориентированного программирования
Классы, атрибуты и операции
Атрибуты класса
Вызов операций класса.
Константы класса
Вызов методов класса
Наследование
Модификаторы доступа
Статические методы
Указание типов
Клонирование объектов
Абстрактные классы
Проектирование классов
Реализация классов
Дополнительная объектно-ориентированная функциональность
Новая объектно-ориентированная функциональность РНР5
Концепции объектно-ориентированного
программирования
Современные языки программирования обычно поддерживают или даже требуют
применения объектно-ориентрованного подхода при разработке программного обес-
печения. В процессе объектно-ориентированной разработки предпринимается по-
пытка задействования классификаций, отношений и свойств объектов системы для
упрощения разработки программ и повторного использования кода.
Классы и объекты
В контексте объектно-ориентированного программного обеспечения объектом
может быть практически любой элемент или концепция — физический объект, по-
добный рабочему’ столу или клиенту, либо концептуальный объект, существующий
только в программе, такой как, например, область ввода текста или файл. В общем
случае наибольший интерес для вас будут представлять объекты, как реальные, так и
концептуальные, которые должны быть представлены в программе.
Объектно-ориентированная программа разрабатывается и создается в виде набо-
ра самостоятельных объектов, имеющих атрибуты и операции, которые взаимодей-
ствуют с целью удовлетворения существующих потребностей. Атрибуты (attributes) —
это свойства или переменные, имеющие отношение к объекту. Операции (operations)
представляют собой методы, действия или функции, которые объект может выпол-
нить с целью модификации самого себя или какого-то внешнего объекта. (Возможно,
вы слышали термин “атрибут” совместно с терминами “переменная-член” и “свойст-
во”, а термин “операция” — совместно с термином “метод”.)
Основное достоинство объектно-ориентированного программного обеспечения
заключается в его способности поддерживать и стимулировать инкапсуляцию (encap-
sulation). которая известна также как сокрытие данных (data hiding). По существу, дос-
туп к данным внутри объекта возможен только через операции объекта, называемые
интерфейсом (interface) объекта.
Действия, выполняемые объектом, распространяются только на используемые им
данные. Можно без труда изменить способы реализации объекта для повышения
производительности, добавления новых свойств или исправления программных
ошибок без необходимости изменения интерфейса. Изменение интерфейса чревато ос-
ложнениями для всего проекта, однако инкапсуляция позволяет вносить изменения и
исправлять ошибки, не касаясь других частей проекта.
В других областях разработки программного обеспечения объектно-ориентиро-
ванный подход является нормой, а процедурное или функционально-ориентирован-
ное программирование считается устаревшим. Большинство Web-сценариев все еще
разрабатывается и создается с применением специализированного подхода, соответст-
вующего функционально-ориентированной методологии.
Применение упомянутого похода обусловлено рядом причин. Многие Web-
проекты относительно невелики и достаточно просты. Вы просто можете взять но-
жовку и соорудить деревянную полочку для специй, не планируя заранее своих дейст-
вий; точно так же вы можете успешно завершить большую часть проектов по разра-
ботке программного обеспечения для Web в силу' их небольших размеров. Однако
182
Часть I. Использование РНР
если вы вооружитесь ножовкой и попытаетесь построить дом, не имея соответст-
вующего плана, вам вряд ли удастся добиться приемлемых результатов; это же спра-
ведливо и в отношении крупных программных проектов.
Многие проекты для Web эволюционируют от простого набора страниц, связан-
ных между собой гиперссылками, к сложным приложениям. Сложные приложения,
независимо от того, представлены ли они некоторым множеством диалоговых окон
или динамически генерируемых HTML-страниц, требуют тщательно продуманной ме-
тодологии разработки. Ориентация на объекты предоставляет возможность управ-
лять сложностью проектов, повышать пригодность кода для многократного исполь-
зования и, в конечном итоге, снижать затраты на сопровождение.
В объектно-ориентированном программном обеспечении объект представляет
собой уникальную и идентифицируемую коллекцию сохраненных данных и опера-
ций, осуществляющих различные действия над этими данными. Например, мы могли
бы иметь два объекта, представляющих кнопки. Даже если обе кнопки одинаково по-
мечены как “ОК”, имеют ширину 60 пикселей и высоту 20 пикселей, и у них совпада-
ют любые другие атрибуты, тем не менее, необходимо иметь возможность работать с
каждой из этих кнопок по отдельности. В программе для этого используются специ-
альные переменные, которые действуют как дескрипторы (уникальные идентификато-
ры) объектов.
Объекты могут быть сгруппированы в классы. Классы представляют некоторые
наборы объектов, которые могут отличаться один от другого по тем или иным при-
знакам, но при этом должны иметь кое-что общее. Классу принадлежат объекты, в
которых одинаковые операции выполняются одинаково, а одинаковые атрибуты
представляют одни и те же свойства, хотя количество этих атрибутов изменяется от
объекта к объекту.
Существительное “велосипед” можно было бы считать классом объектов, описы-
вающим множество различных велосипедов, которые имеют много общих свойств
или атрибутов, таких как свойство иметь два колеса, цвет и размер, а также выпол-
няемые ими операции, например, передвижение.
Ваш собственный велосипед можно считать объектом, который принадлежит к
классу велосипедов. Он имеет все общие свойства, присущие всем велосипедам,
включая операцию передвижения, которая выполняется так же, как у большинства
остальных велосипедов, даже если ваш велосипед используется намного реже. Атри-
буты вашего велосипеда имеют уникальные значения, поскольку он окрашен в зеле-
ный цвет, а далеко не все велосипеды имеют этот цвет.
Полиморфизм
Объектно-ориентированный язык программирования должен поддерживать по-
лиморфизм (polymorphism), то есть, возможность выполнять одну и т}' же операцию в
различных классах по-разному (иначе говоря, ее поведение в различных классах мо-
жет отличаться). Например, в классах автомобилей и велосипедов операции пере-
движения различаются. Что касается реальных объектов, то это редко становится
источником каких-то проблем. Едва ли можно спутать велосипеды с автомобилями и
применить к ним операцию передвижения автомобиля. В то же время обычный здра-
вый смысл в отношении языков программирования действует не всегда, поэтому
Глава 6. Объвктно-оривнтированное программирование на РНР
183
язык такого типа должен поддерживать полиморфизм, чтобы знать, какую операцию
передвижения нужно использовать применительно к конкретному объекту.
Полиморфизм — это скорее характеристика поведения, нежели объектов. В РНР
только функции-члены класса могут быть полиморфными. Сравнение в реальном ми-
ре равносильно сравнению глаголов в естественных языках, которые эквивалентны
функциям-членам. В реальном мире велосипед, помимо всего прочего, можно чис-
тить, передвигаться на нем, разбирать, ремонтировать или красить.
Эти глаголы описывают общие действия, поскольку вы не знаете, к какому объекту
они применяются. (Абстракция объектов и действий подобного рода — одна из отли-
чительных характеристик человеческого разума.)
Например, для езды на велосипеде требуется выполнение совершено иных дейст-
вий, чем для езды в автомобиле, несмотря на то, что эти понятия аналогичны. Глагол
ездить можно связать с конкретным набором действий только после того, как станет
известен объект, к которому он применяется.
Наследование
Наследование (inheritance) позволяет с использованием субклассов (subclasses) созда-
вать иерархические взаимосвязи между классами. Субкласс наследует атрибуты и
операции своего суперкласса (superclass). Например, автомобиль и велосипед имеют
некоторые общие характеристики. Мы могли бы создать класс транспортных
средств, содержащий такие характеристики, как атрибут цвета и операцию передви-
жения, свойственные всем транспортным средствам, а затем унаследовать от класса
транспортных средств классы автомобиля и велосипеда.
Понятия субкласса, производного класса и дочернего класса будут применяться
попеременно. Точно так же попеременно будут использоваться понятия суперкласса
и родительского класса.
Благодаря наследованию вы можете создавать надстройки и дополнения к сущест-
вующим классам. По мере необходимости из простого базового класса вы можете полу-
чить более сложные и специализированные производные классы. Данная возможность
делает ваш код более пригодным для многократного использования, что является од-
ним из наиболее важных достоинств объектноориентированного подхода.
Использование наследования позволяет уменьшить объем выполняемой работы,
если операции можно реализовывать лишь один раз в суперклассе вместо многократ-
ного их создания в отдельных субклассах. Наследование также позволяет более точно
моделировать отношения, существующие в реальном мире. Если применительно к
двум классам выражение “является” имеет смысл, то вполне вероятно, что в этом слу-
чае наследование возможно. Выражение “автомобиль является транспортным сред-
ством” имеет смысл, однако в выражении “транспортное средство является автомо-
билем” смысла мало, поскольку' не все транспортные средства представляют собой
автомобили. Следовательно, автомобиль может быть унаследован от транспортного
средства.
184
Часть I. Использование РНР
Создание классов, атрибутов
и операций в РНР
До сих пор мы рассматривали классы на довольно-таки высоком уровне абстрак-
ции. При создании класса в РНР вы должны использовать ключевое слово class.
Структура класса
Минимальный вариант определения класса имеет следующий вид:
class classname
{
}
Чтобы от классов была хоть какая-нибудь польза, они должны иметь атрибуты и
операции. Атрибуты создаются путем объявления переменных внутри определения
класса с помощью ключевого слова var. Следующий код создает класс classname с
атрибутами Sattributel и $attribute2.
class classname
{
var Sattributel;
var Sattribute2;
}
Операции создаются за счет объявления функций внутри определения класса.
Следующий код создает класс classname с двумя операциями, которые не выполняют
никаких действий. Операция operationl () не принимает никаких параметров, а
операция operation2 () принимает два параметра.
class classname
{
function operationl()
{
}
function operation?($paraml, $param2)
{
}
}
Конструкторы
В большинстве классов имеется специальный тип операции, получивший назва-
ние конструктора. Конструктор (constructor) вызывается в тех случаях, когда нужно
создать объект; обычно он выполняет такие полезные задачи по инициализации, как
установка осмысленных начальных значений атрибутов или создание других объек-
тов, требуемых для данного объекта.
Конструктор объявляется так же, как другие операции, но имеет специальное имя
__construct (). Это нововведение РНР5. В предыдущих версиях РНР функция конст-
руктора получала то же имя, что и класс. Для обратной совместимости, если в классе
не определена функция _construct)), выполняется поиск функции, имя которой
совпадает с именем самого класса.
Глава 6. Объектно-ориентированное программирование на РНР
185
Хотя конструктор можно вызывать вручную, его основное значение заключается в
том, что он автоматически вызывается в момент создания объекта. Следующий код
объявляет класс с конструктором:
class classname
{
function _____construct(Sparam)
{
echo “Конструктор вызван с параметром $param <br />";
)
}
В настоящее время РНР поддерживает перегрузку функций, то есть можно опре-
делять более одной функции с одним и тем же именем и различным количеством или
типами параметров. (Данное свойство характерно для многих объектно-ориентиро-
ванных языков.) Дополнительная информация представлена в следующих разделах.
Деструкторы
Деструктор представляет собой противоположность конструктору. Механизм де-
структоров появился в РНР5. Они позволяют выполнять определенные действия не-
посредственно перед уничтожением класса. Деструктор вызывается автоматически,
когда все ссылки на класс удаляются или выходят за пределы области видимости.
Подобно именованию конструкторов, деструктор класса должен иметь имя
__destruct (). Деструкторы не могут принимать параметры.
Создание экземпляров класса
После объявления класса необходимо создать объект — конкретный индивидуаль-
ный экземпляр, являющийся членом класса, с которым будет выполняться работа. Этот
процесс называют также созданием экземпляров (instantiating) класса. Объект создается с
помощью ключевого слова new. При этом нужно указать, экземпляром какого класса
будет объект, и предоставить все параметры, которые необходимы конструктору.
Приведенный ниже код объявляет класс classname с конструктором, а затем соз-
дает три объекта типа classname:
class classname
{
function ___construct(Sparam)
{
echo "Конструктор вызван с параметром Sparam <br />";
}
}
$a = new classname('Первый');
$b = new classname('Второй');
$c = new classname();
Поскольку конструктор вызывается каждый раз, когда создается объект, этот код
генерирует следующий вывод:
Конструктор вызван с параметром Первый
Конструктор вызван с параметром Второй
Конструктор вызван с параметром
186
Часть I. Использование РНР
Использование атрибутов класса
Внутри класса вы получаете доступ к специальном}7 указателю с именем $this. Ес-
ли атрибут вашего текущего класса имеет имя $attribute, вы ссылаетесь на него при
установке или при обращении к переменной из операции внутри класса следующим
образом:
$this->attribute
Следующий код служит примером установки атрибутов и обращения к ним внутри
класса:
class classname
{
var $attribute;
function operation($param)
{
$this->attribute = $param
echo $this->attribute;
}
}
Возможность доступа к атрибуту извне класса определяется модификаторами дос-
тупа, которые обсуждаются далее в главе. Класс, код которого приведен выше, никак
не ограничивает доступ к атрибуту, поэтому к нему можно обратиться так, как пока-
зано ниже:
class classname
{
var $attribute;
}
$a = new classname();
$a->attribute = 'value';
echo $a->attribute;
Прямой доступ к атрибутам за пределами класса — обычно не особенно хорошая
идея. Одно из преимуществ объектно-ориентированного подхода заключается в том,
что он поощряет инкапсуляцию. Достигнуть это можно с помощью функций____get и
__set. Если вместо прямого доступа к атрибутам класса создать функции доступа (ac-
cessor functions), весь доступ будет осуществляться через один раздел программного
кода. Первоначальный вариант функций доступа может иметь следующий вид:
class classname
{
var $attribute;
function _get($name)
(
return $this->$name;
)
function _set($name, $value)
(
$this->$name = $value;
}
}
Глава 6. Объектно-ориентированное программирование на РНР
187
Этот код предоставляет минимальные функции для доступа к атрибуту с именем
$attribute. Функция ___get () просто возвращает значение атрибута $attribute, а
функция___set () присваивает ему новое значение.
Заметьте, что функция______get () принимает один параметр — имя атрибута — и воз-
вращает значение этого атрибута. Аналогично, функция___set () принимает два па-
раметра — имя атрибута и новое значение, которое должно быть ему' присвоено.
Вы не должны вызывать эти функции напрямую. Два символа подчеркивания в
начале имен функций означают, что они имеют в РНР специальное назначение по-
добно функциям____construct () и_destruct ().
Рассмотрим, как работают эти функции. Если вы создаете экземпляр класса:
$а = new classname();
то можете затем использовать функции____get () и_set () для проверки и установки
значения любого атрибута.
Показанный ниже оператор:
$a->$attribute = 5;
приведет к неявному вызову функции___set () с передачей ей "attribute" в качестве
значения параметра $name и 5 в качестве значения параметра $value. Внутри функ-
ции __set () вы должны обеспечить все необходимые проверки на ошибочные зна-
чения.
Функция____get () работает аналогичным образом. Если где-то в коде присутствует
ссылка на атрибут:
$a->$attribute
осуществляется неявный вызов функции____get () с передачей ей "attribute" в каче-
стве значения параметра $паше. Это приведет к возврату функцией_get () значения
атрибута.
На первый взгляд может показаться, что от этого кода совсем немного пользы.
Возможно, что в том виде, в каком он здесь приведен, это так и есть, однако причина
применения функций доступа объясняется просто: обращение к конкретному' атрибу-
ту реализуется через всего лишь один раздел кода.
11ри наличии только одной точки доступа можно организовать проверку на допус-
тимость, дабы гарантировать, что сохраняются лишь данные, имеющие смысл. Если
впоследствии выяснится, что значение атрибута $attribute может лежать только в
диапазоне между 0 и 100, можно только один раз добавить несколько строк кода и реа-
лизовать проверку перед тем, как разрешить изменение. Теперь функции ____set ()
можно придать такой вид:
function___set($name, $value)
{
if ( $name='attribute' && $value >= 0 && $value <= ICO )
$this->attribute = $value;
)
Имея единственную точку доступа, мы получаем возможность легко вносить изме-
нения в лежащую в основе реализацию. Если по какой-либо причине мы решим поме-
нять способ сохранения атрибута $attribute, функции доступа позволяют сделать
это. изменив код только в одном месте.
188
Часть I. Использование РНР
Может возникнуть необходимость вместо хранения $attribute в виде перемен-
ной извлекать ее из базы данных, когда это нужно, вычислять текущее значение при
каждом ее запросе, вычислять различные значения на базе значений других атрибу-
тов или кодировать данные в виде более короткого типа данных. Какое бы изменение
ни требовалось, достаточно лишь соответствующим образом модифицировать функ-
ции доступа. Никакого влияния на другие разделы программы не будет оказываться
до тех пор, пока функции доступа будут принимать и возвращать данные, ожидаемые
в других частях кода.
Управление доступом с помощью
модификаторов private и public
В РНР5 введены модификаторы доступа, которые управляют видимостью атрибу-
тов и методов. Модификатор доступа указывается перед объявлением атрибута или
метода. РНР5 поддерживает следующие три различных модификатора доступа:
Модификатор доступа public (общедоступный), устанавливаемый по умолчанию,
означает открытый доступ к атрибуту или методу, что сродни полному отсутствию
модификатора доступа. Элементы подобного рода доступны как изнутри, так и извне
класса.
Модификатор доступа private (приватный) означает, что помеченный им элемент
может быть доступен только изнутри класса. Его можно применять для всех атрибутов,
если не используются функции доступа_get () и_set (). В отношении методов дан-
ный модификатор указывается, если тот или иной метод является служебным и пред-
назначенным только для внутренних целей класса. Приватные элементы не наследуют-
ся (более подробно о наследовании рассказывается далее в этой главе).
Модификатор доступа protected (защищенный) означает, что помеченный им
элемент может быть доступен только изнутри класса. Он также существует во всех
субклассах; вопросы, связанные с его использованием рассматриваются ниже в главе.
Сейчас protected можно воспринимать как нечто среднее между public и private.
Измените код класса, рассмотренного выше в качестве примера, чтобы добавить
модификаторы доступа:
class classname
{
public Sattribute;
public function _get($name)
{
return Sthis->Sname;
}
public function _set (Sname, Svalue)
{
Sthis->$name = Svalue;
}
Сейчас каждый элемент класса предварен соответствующим модификатором дос-
тупа. Ключевые слова public можно не указывать, поскольку этот модификатор при-
нимается по умолчанию, однако, если в коде присутствуют и другие модификаторы
доступа, то указание public может упростить чтение всего кода.
Глава 6. Объектно-ориентированное программирование на РНР
189
Обратите внимание на отсутствие ключевого слова var, которое просто заменено
модификатором public. В данном примере все элементы были сделаны общедоступ-
ными.
Вызов операций класса
Операции класса можно вызывать в основном тем же способом, каким вызывают-
ся атрибуты класса. Если в нашем распоряжении имеется следующий класс:
class classname
{
function operation!()
{
}
function operation?($paraml, $param2)
{
}
}
и мы создаем объект типа classname с именем $а следующим образом:
$а = new classname();
то мы можем вызывать операции так же, как вызывали другие функции: используя их
имя и указывая в круглых скобках любые требуемые ими параметры. Поскольку эти
операции принадлежат объекту, а не являются обычными функциями, необходимо
указать объект, к которому они относятся. Имя объекта используется так же, как ат-
рибуты объекта, а именно:
$a->operationl();
$a->operation2(12, 'test');
Если операции что-то возвращают, то возвращаемые данные можно получить сле-
дующим образом:
$х = $a->operationl();
$у = $a->operation2(12, 'test');
Реализация наследования в РНР
Если класс должен быть субклассом другого класса, для указания этого факта ис-
пользуется ключевое слово extends. Приведенный ниже код создает класс В, унасле-
дованный от ранее определенного класса А.
class В extends А
{
var $attribute2;
function operation2()
{
}
}
190
Часть I. Использование PHP
Если класс А объявлен следующим образом:
class А
{
var $attributel;
function operationl()
{
}
}
то все показанные ниже обращения к операциям и атрибутам объекта типа В будут
допустимыми:
$b = new В();
$b->operationl();
$b->attributel = 10;
$b->operation2();
Sb->attribute2 = 10;
Обратите внимание, что поскольку класс В является расширением класса А, мы
можем ссылаться на операцию operationl () и атрибут $attribute, несмотря на то
что они были объявлены в классе А. Будучи субклассом класса А, класс В обладает всей
функциональностью и данными класса А. Наряду с этим, класс В объявляет свой соб-
ственный атрибут и операцию.
Важно отметить, что наследование работает только в одном направлении. Суб-
класс, или дочерний класс, наследует свойства родительского класса, или суперклас-
са, однако родительский класс не получает свойств своего дочернего класса. Это оз-
начает, что две последних строки в следующем фрагменте кода ошибочны:
$а = new А (} ;
$a->operationl();
$a->attributel = 10;
$a->operation2();
$a->attribute2 = 10;
Класс А не имеет ни операции operation2, ни атрибута attribute2.
Управление видимостью при наследовании с помощью
модификаторов private и protected
Модификаторы доступа private и protected могут использоваться для управле-
ния наследованием. Если атрибут или метод определен как private, наследоваться он
не будет. Если же атрибут или метод определен как protected, он будет наследовать-
ся, однако не будет видим извне класса (подобно элементу private).
Рассмотрим следующий пример:
<?php
class А
{
private function operationl()
{
echo "Вызвана операция operationl”;
}
Глава 6. Объвктно-оривнтированнов программирование на РНР
191
protected function operation2()
{
echo "Вызвана операция operation2";
)
public function operation!()
{
echo "Вызвана операция operation!";
)
)
class В extends A
{
function ____construct()
{
$this->operationl();
$this->operation2();
$this->operation!();
}
)
$b = new B;
?>
Этот код создает в классе А по одной операции каждого типа: public, protected
и private. Класс В наследуется от класса А. В рамках конструктора класса В предпри-
нимаются попытки обратиться к операциям родительского класса.
Строка
$this->operationl();
приводит к следующей фатальной ошибке:
Fatal error: Call to private method A::operationl() from context ’ =
Фатальная ошибка: Вызов приватного метода А::operationl() из контекста В'
Этот пример показывает, что приватные операции, определенные в родитель-
ском классе, нельзя вызывать из дочернего класса.
Если закомментировать эту строку кода, остальные две будут работать нормально.
Функция с модификатором protected наследуется, однако может использоваться
только внутри дочернего класса, что, собственно, и сделано. В результате добавления
следующей строки:
$b->operation2();
в конец файла, будет сгенерирована такая ошибка:
Fatal error: Call to protected method A::operation2() from context ''
Фатальная ошибка: Вызов защищенного метода А::operation2() из контекста ' '
Вместе с тем, операцию operation! () можно вызывать и за пределами класса:
$b->operation!();
Данный вызов возможет, поскольку operationl () объявлена как public.
192
Часть I. Использование РНР
Перекрытие
В этой главе мы рассмотрели субкласс, в котором объявляются новые атрибуты и
операции. Допустимо, а иногда и полезно повторно объявлять ранее объявленные
атрибуты и операции. Это может делаться с целью присвоения атрибуту в субклассе
значения, которое отличается от значения по умолчанию того же атрибута в супер-
классе, или для предоставления той или иной операции в субклассе функциональных
возможностей, отличных от функциональных возможностей той же операции в су-
перклассе. Упомянутый процесс носит название перекрытия (overriding).
Рассмотрим, например, класс А со следующим определением:
class А
{
var $attribute = 'значение по умолчанию';
function operation!)
(
echo 'Что-то здесь такое<Ьг />';
echo "Значением \$attribute является $this->attribute<br />";
)
)
Необходимо изменить значение атрибута $attribute, назначенное по умолча-
нию, и наделить операции operation() новыми функциональными возможностями.
Для этого можно создать показанный ниже класс В, в котором перекрываются атри-
бут $attribute и операция operation ():
class В extends А
{
var $attribute = 'другое значение';
function operation()
{
echo 'А здесь что-то другое<Ьг />';
echo "Значением \$attribute является $this->attribute<br />";
}
)
Объявление класса В не влияет на исходное определение класса А. Рассмотрим
следующие две строки кода:
$а = new А();
$а -> operation!);
Эти строки создают объект типа А и вызывают его функцию operation!). В ре-
зультате получается следующий вывод:
Что-то здесь такое
Значением Sattribute является значение по умолчанию
доказывающий отсутствие влияния класса В на класс А. Создав объект типа В, получим
другой вывод.
Следующий код:
$Ъ = new В();
$Ъ -> operation!);
Глава 6. Объектно-ориентированное программирование на РНР
193
генерирует такой вывод:
А здесь что-то другое
Значением $attribute является другое значение
Как объявление новых атрибутов или операций в субклассе не оказывает влияния
на суперкласс, так и перекрытие атрибутов или операций в субклассе не оказывает
влияния на суперкласс.
Субкласс наследует все атрибуты и операции своего суперкласса, если вы не пре-
дусматриваете их замены. Если вы определяете замен}', она получает приоритет и
перекрывает исходное определение.
Ключевое слово parent позволяет обращаться к исходной версии операции в ро-
дительском классе. Например, для вызова А: :operationl) внутри класса В использу-
ется следующий код:
parent: : operation ()
Вывод, однако, будет другим. Несмотря на то что вызывается операция родитель-
ского класса, РНР использует значения атрибутов из текущего класса. Таким образом,
вывод получается следующим:
Что-то здесь такое
Значением $attribute является другое значение
Наследование может достигать нескольких уровней в глубину’. Можно объявить
класс, скажем. С, который расширяет класс В и, следовательно, наследует свойства
класса В и класса А, родительского по отношению к классу В. В классе С можно также
выборочно перекрывать и заменять атрибуты и операции, унаследованные от роди-
тельских классов.
Предотвращение наследования и перекрытия
с помощью final
В РНР5 было введено ключевое слово final. Когда это слово используется перед
объявлением функции, эта функция не может быть перекрыта ни в одном субклассе.
Чтобы проиллюстрировать это, добавим final в код класса А из предыдущего примера:
class А
{
var $attribute = 'значение по умолчанию';
final function operation!)
{
echo 'Что-то здесь такоесЬг />';
echo "Значением \$attribute является $this->attribute<br />";
}
}
В результате перекрывать operation!) в классе В уже будет нельзя. Если вы попы-
таетесь это сделать, то получите ошибку следующего вида:
Fatal error: Cannot override final method A::operation!)
Фатальная ошибка: Невозможно перекрыть финальный метод А::operation()
194
Часть I. Использование РНР
Можно также запретить создавать субклассы на основе заданного класса, помес-
тив ключевое слово final перед определением класса, например:
final class А
{ ... }
При попытке унаследовать класс от А будет генерироваться ошибка:
Fatal error: Class В may not inherit from final class (A)
Фатальная ошибка: Класс В не может быть унаследован от финального класса (А)
Множественное наследование
В некоторых объектно-ориентированных языках (например, C++ и Smalltalk) под-
держивается множественное наследование, хотя РНР его не поддерживает. Это озна-
чает, что каждый класс может наследовать характеристики только от одного роди-
тельского класса. Количество дочерних классов, имеющих общий родительский
класс, не ограничивается.
Возможно, сказанное будет понятно не сразу. На рис. 6.1 показаны три возможных
способа наследования для трех классов А, В и С.
Одиночное
наследование
Множественное
наследование
Одиночное
наследование
Рис. 6.1. РНР не поддерживает множественное наследование
Комбинация слева на рис. 6.1 показывает, что класс С наследуется от класса В, ко-
торый, в свою очередь, наследуется от класса А. Каждый класс имеет не более одного
родительского класса, следовательно, это вполне допустимое в РНР одиночное на-
следование.
Комбинация в центре на рисунке отражает, что классы В и С наследуются от
класса А. Каждый класс имеет не более одного родительского класса, следовательно,
и это допустимое одиночное наследование.
Комбинация справа показывает, что класс С унаследован от двух классов А и В.
В этом случае класс С имеет два родительских класса, следовательно, это множест-
венное наследование, которое в РНР не поддерживается.
Глава 6. Объектно-ориентированное программирование на РНР
195
Реализация интерфейсов
В РНР5 появилось понятие интерфейса (interface). Интерфейс рассматривается
как искусственная замена множественного наследования и подобен концепции ин-
терфейсов, которая поддерживается в других объектно-ориентированных языках,
таких как Java.
Основная идея интерфейса состоит в спецификации набора функций, которые
должны быть реализованы в классе, в котором определен данный интерфейс. На-
пример. вы можете решить, что какой-то набор классов должен иметь возможность
отображать себя. Вместо того чтобы создавать родительский класс с функцией
display!), которую затем должны унаследовать и перекрыть все классы из набора,
можно реализовать интерфейс, как показано ниже:
interface Displayable
{
function display!);
}
class webPage implements Displayable
{
function displayO
{
// ...
}
)
Этот пример демонстрирует обходной путь реализации множественного наследо-
вания, поскольку класс webPage можег быть унаследован от одного класса и реализо-
вывать один или более интерфейсов.
Если не реализовать методы, указанные в интерфейсе (в данном случае display ()),
генерируется фатальная ошибка.
Проектирование классов
Теперь, когда вы ознакомились с некоторыми понятиями и идеями, лежащими в
основе объектов и классов, и с синтаксисом их реализации в РНР, можно переходить
к изучению подходов к проектированию полезных классов.
Многие классы в коде будут представлять классы или категории объектов реаль-
ного мира. Классы, которыми вы, возможно, воспользуетесь при разработке Web-
приложений, могут включать страницы, компоненты пользовательского интерфейса,
покупательские тележки, обработчики ошибок, категории товаров или клиентов.
Объекты в вашем коде могут также представлять конкретные экземпляры ранее
упомянутых классов — например, домашнюю страницу7, отдельную кнопку или поку-
пательскую тележку, которой некто Билли Боне пользуется в конкретный момент
времени. Сам Билли Боне может быть представлен объектом типа клиент. Каждый
товар, который покупает Билли, может быть представлен объектом, принадлежащим
определенной категории или классу7.
В предыдущей главе простые включаемые файлы использовались для обеспечения
единообразного внешнего вида различных страниц Web-сайта вымышленной компа-
нии TLA Consulting. Воспользовавшись классами, а также возможностью экономить
196
Часть I. Использование РНР
время, предоставляемой механизмом наследования, вы можете создать более совер-
шенную версию того же сайта.
Мы хотим иметь возможность быстро создавать страницы для сайта TLA Consulting,
которые сохраняют единый стиль оформления и ведут себя одинаково. В то же время
страницы должно быть без труда изменять, приводя их в соответствие различным
частям сайта.
Для целей данного примера мы создадим класс Раде (страница). Основное назна-
чение этого класса — ограничить объем HTML-кода, требуемого для создания новой
страницы. Он должен допускать модификацию тех частей страниц, которые изменя-
ются от страницы к странице, и обеспечивать автоматическую генерацию тех фраг-
ментов, которые остаются неизменными на каждой странице.
Класс должен предоставлять гибкую структуру для построения новых страниц, но
при этом не должен ограничивать нам свободу действий.
Поскольку мы генерируем страницу с помощью сценария, а не статического
HTML, мы можем воспользоваться любым количеством полезных средств, в том чис-
ле новыми функциональными возможностями, которые позволяют нам выполнять
следующие действия:
Изменять элементы страницы только в одном месте. Например, если мы вно-
сим изменения в примечания относительно авторских прав или добавляем до-
полнительную кнопку, мы должны вносить соответствующее изменение только
в каком-то одном месте.
Иметь содержимое по умолчанию для большей части страницы и при этом
иметь возможность при необходимости менять любой элемент, устанавливая
пользовательские значения для таких элементов, как, например, заголовок и
метадескрипторы.
Распознавать, какая страница просматривается, и менять соответствующим
образом значения навигационных элементов, например, нет смысла устанав-
ливать на домашней странице кнопку, щелчок на которой вызывает переход на
ту же домашнюю страницу.
Замещать стандартные элементы на конкретных страницах. Например, если в
определенных разделах сайта требуются другие навигационные кнопки, мы
должны иметь возможность замещать ими стандартные кнопки.
Написание кода класса
После того, как мы определили, каким должен быть вывод разрабатываемого кода
и какие функции он должна выполнять, как реализовать этот код?
Позже в этой книге речь пойдет о проектировании и управлении крупными про-
ектами. А пока мы сосредоточим внимание на особенностях написания объектно-
ориентированного РНР-кода.
Классу необходимо присвоить логическое имя. Поскольку он представляет стра-
ницу, назовем его Раде. Для объявления класса Раде следует ввести:
class Раде
{
}
Глава 6. Объектно-ориентированное программирование на РНР
197
Разрабатываемому классу нужны атрибуты. Элементы, которые, возможно, при-
дется изменять от страницы к странице, мы определим как атрибуты класса. Ос-
новное содержимое страницы, которое будет представлено комбинацией HTML-
дескрипторов и текста, назовем $content. Это содержимое можно объявить посред-
ством следующей строки кода внутри определения класса:
public Scontent;
Мы можем также определить атрибуты для хранения заголовка страницы. По-
видимому, их придется изменять, чтобы посетитель четко знал, какую страницу он
просматривает. Вместо использования пустых заголовков, мы зададим заголовок по
умолчанию с помощью следующего объявления:
public Stitle = 'TLA Consulting Pty Ltd';
Большинство коммерческих Web-страниц включают в себя метадескрипторы, по-
могающие поисковым механизмам выполнять их индексацию. Чтобы они были по-
лезны, метадескрипторы, по-видимому, должны изменяться от страницы к странице.
В этом случае мы также определяем значение по умолчанию:
public Skeywords = 'TLA Consulting, Three Letter Abbreviation,
поисковые механизмы — мои лучшие друзья';
Навигационные кнопки, показанные на исходной странице, изображенной на
рис. 5.2 (см. предыдущую главу), скорее всего, должны оставаться неизменными на
всех страницах, чтобы посетители не путались; однако, чтобы их можно было легко
изменить, мы также должны их сделать атрибутами. Поскольку количество кнопок
может меняться, мы объявляем массив и сохраняем в нем как текст кнопки, так и URL
(Uniform Resource Locator — унифицированный указатель информационного ресур-
са), на который она должна указывать.
public Sbuttons = array( 'Домой' => 'home.php',
'Контакт' => 'contact.php',
'Услуги' => ’services.php',
'Карта сайта' => 'map.php'
) ;
Чтобы иметь возможность выполнять те или иные функции, классу также необхо-
димы операции. Их определение можно начать с определения функций доступа,
обеспечивающих установку и получения значений атрибутов, которые мы определи-
ли. Все функции принимают следующий вид:
public function__set($name, Svalue)
{
$this->$name = Svalue;
}
Функция____set() не содержит проверки на ошибки (для краткости), однако дан-
ную возможность при необходимости можно легко добавить. Поскольку маловероят-
но, что любые из этих значений будут запрашиваться извне класса, мы решили не оп-
ределять функцию____get ().
Основное назначение этого класса заключается в том, чтобы отображать HTML-
страницы, а для этого необходима функция. Она получает имя Display!) и принима-
ет следующий вид:
198
Часть I. Использование РНР
public function Display!)
{
echo "<html>\n<head>\n”;
$this -> DisplayTitle!);
$this -> DisplayKeywords!);
$this -> Displaystyles();
echo "</head>\n<body>\n";
$this -> DisplayHeader!);
$this -> DisplayMenu($this->buttons);
echo $this->content;
$this -> DisplayFooter():
echo ”</body>\n</html>\n";
Данная функция включает в себя несколько простых операторов echo для ото-
бражения HTML-текста, но в основном состоит из вызовов других функций класса.
Как нетрудно догадаться по их именам, эти функции отображают различные части
страницы.
Совсем не обязательно организовывать функции таким образом. Все эти отдель-
ные функции можно было бы объединить в одну большую функцию. Мы же ввели та-
кое разделение по ряде’ причин.
Каждая функция должна выполнять определенную задачу7. Чем проще эта задача,
тем проще создавать и тестировать функцию. Но не следует заходить слишком далеко
в этом направлении — если вы разобьете программу на очень большое число неболь-
ших фрагментов, ее будет трудно читать.
Используя наследование, мы можем выполнять перекрытие операций. Можно за-
местить одну крупную функцию Di splay (), однако маловероятно, чтобы мы захотели
изменить способ отображения всей страницы. Гораздо рациональнее разбить дейст-
вия по отображению на несколько самостоятельных задач и иметь возможность вы-
полнять перекрытие только тех частей, которые требуется изменить.
Функция Display (отобразить) вызывает функции DisplayTitle!) (отобразить
заголовок), DisplayKeywords!) (отобразить ключевые слова), Displaystyles () (ото-
бразить стили), DisplayHeader!) (отобразить верхний колонтитул), DisplayMenu!)
(отобразить меню) и DisplayFooter!) (отобразить нижний колонтитул). Это означа-
ет, что необходимо определить эти операции. Операции или функции можно запи-
сывать в этом логическом порядке, вызывая операцию или функцию еще до того, как
в программе встретится фактический код этой операции или функции. Во многих
других языках код функции или операции должен быть записан до ее вызова. Боль-
шинство используемых в данном случае операций весьма просты и необходимы для
отображения некоторого HTML-текста и, возможно, содержимого атрибутов.
В листинге 6.1 приводится завершенный класс, который сохранен в файле
page . inc. В таком виде он может быть затребован или включен в другие файлы.
Листинг 6.1. page .inc — класс Раде, предоставляющий простой и гибкий способ
создания страниц сайта TLA Consulting
<?php
class Page
{
Глава 6. Объектно-ориентированное программироаание на РНР
199
// атрибуты класса Page
public $content;
public $title = 'TLA Consulting Pty Ltd';
public $keywords = 'TLA Consulting, Three Letter Abbreviation,
поисковые механизмы - мои лучшие друзья';
public $buttons = array( 'Домой' => 'home.php',
'Контакт' => 'contact.php',
'Услуги' => 1 services.php',
'Карта сайта' => 1 map.php1
);
// операции класса Page
public function ___set($name, $value)
{
$this->$name = $value;
}
public function Display()
(
echo “<html>\n<head>\n";
$this -> DisplayTitle();
$this -> DisplayKeywords();
$this -> Displaystyles();
echo "</head>\n<body>\n";
$this -> DisplayHeader() ;
$this -> DisplayMenu($this->buttons);
echo $this->content;
$this -> DisplayFooter();
echo "</body>\n</html>\n";
)
public function DisplayTitle()
{
echo '<title> '.$this->title.' </title>';
}
public function DisplayKeywords()
{
echo "<meta name=\"keywords\" content=\
.html entities ($this->keywords) . " \" />";
J
public function Displaystyles()
{
<style>
hl {coloriwhite; font-size:24pt; text-align:center;
font-family:arial,sans-serif}
.menu (color:white; font-size:12pt; text-align:center;
font-family:arial,sans-serif; font-weight:bold}
td {background-.black}
p {color:black; font-size:12pt; text-align:justify;
font-family:arial,sans-serif}
p.foot {color:white; font-size:9pt; text-align:center;
font-family:arial,sans-serif; font-weight:bold}
a:link,a:visited,a:active {color:white)
200
Часть I. Использование PHP
</style>
<?php
}
public function DisplayHeader()
{
?>
«table width="100%" cellpadding =”12" cellspacing = "0" border ="0">
<tr bgcolor ="black”>
<td align ="left">«img src = "logo.gif” /></td>
<td>
<hl>TLA Consulting Pty Ltd</hl>
</td>
<td align = nright"ximg src = "logo.gif" /></td>
</tr>
</table>
<?php
}
public function DisplayMenu($buttons)
{
echo "ctable width = '100%' bgcolor = 'white' cellpadding = '4'
cellspacing = 4>\n";
echo " <tr>\n";
// вычисление размеров кнопки
$width = 100/count(Sbuttons);
while (list($name, $url) = each*($buttons) )
{
$this -> DisplayButton($width, $name, $url,
!$this->IsTJRLCurrentPage($url) ) ;
}
echo " </tr>\n";
echo "</table>\n";
public function IsURLCurrentPage($url)
{
if(strpos( $_SERVER['PHP_SELF’], $url )==false)
{
return false;
}
else
{
return true;
}
public function DisplayButton($width, $name, $url, $active = true)
{
if ($active)
{
echo "<td width ='".htmlentities($widthi."%'>
Глава 6. Объектно-ориентированное программирование на РНР
201
<a href =’”.htmlentities($url)."'>
<img src =’s-logo.gif 1 alt =’".htmlentities(Sname)’
border = ’0’ /></a>
<a href =’".htmlentities!$url)'xspan class='menu'>$name«/span>
</a>«/td>" ;
)
else
{
echo "<td width ='11 .htmlentities ($width) >
<img src ='side-logo.gif1>
«span class='menu'>$name</spanx/td>;
}
}
public function DisplayFooter!)
{
«table width = "100%" bgcolor = "black" cellpadding ="12” border = "0">
<tr>
<td>
<p class="foot">© TLA Consulting Pty Ltd.«/p>
<p class="foot">Пожалуйста, просмотрите
<a href="legal .рЬ.р">нашу страницу с официальной информацией </ах/р>
</td>
</tr>
</table>
<?php
}
При просмотре этого листинга обратите внимание, что функции DisplayStyles (),
DisplayHeader!) и DisplayFooter!) должны отображать большой блок статиче-
ского HTML-кода без какой-либо обработки с помощью РНР. Поэтому внутри
функций мы просто указали завершающий РНР-дескриптор (?>), ввели HTML-код,
а затем с помощью открывающего PHP-дескриптора (<?php) снова перешли к РНР.
В этом классе определены еще две операции. Операция DisplayButton () выводит
одиночную кнопку меню. Если кнопка указывает на текущую странице7, вместо нее
отображается неактивная кнопка, которая выглядит несколько иначе и не связана ни
с какими другими страницами. Это обеспечивает единообразный внешний вид стра-
ниц и позволяет посетителям визуально определять местонахождение.
Операция IsURLCurrentPage () определяет, указывает ли связанный с кнопкой URL
на текущую страницу. Для этого служит множество технологий. Мы воспользовались
строковой функцией s trpos (), чтобы определить, содержится ли данный URL в одной
из переменных, установленных сервером. Оператор strpos! $_SERVER[PHP_SELF' j ,
$url ) возвращает число, если строка, хранящаяся в переменной $url, присутствует
внутри суперглобальной переменной $_SERVER [ PHP_SELF' 1, либо значение false, если
это не так.
Чтобы можно было пользоваться классом Раде, файл page, inc потребуется вклю-
чить в сценарий и вызвать Display ().
202
Часть I. Использование РНР
Код, показанный в листинге 6.2, создает домашнюю страницу сайта компании TLA
Consulting и обеспечивает вывод, который очень похож на сгенерированный ранее
(см. рис. 5.2).
Листинг 6.2. home.php —эта домашняя страница использует класс Page
для выполнения большей части действий, требуемых для ее генерации
<?php
require ('page.inc 1);
$homepage = new Page();
$homepage->content = 1<р>Добро пожаловать на сайт компании TLA Consulting.
Пожалуйста, уделите некоторое время на знакомство с нами.</р>
<р>Мы специализируемся на обслуживании ваших деловых
нужд и надеемся скоро вас увидеть снова.</р>’
$homepage->Display() ;
?>
Код в листинге 6.2 выполняет следующие действия:
1. Использует оператор require для включения содержимого файла page.inc,
который содержит определение класса Раде.
2. Создает экземпляр класса Раде. Этому экземпляру назначается имя $homepage.
3. Устанавливает содержимое, включающее некоторый текст и HTML-дескрип-
торы, которые должны быть на странице. (При этом неявно вызывается
_____set.)
4. Вызывает операцию Display () внутри объекта $homepage, которая обеспечи-
вает отображение страницы в окне браузера посетителя.
Как видно из листинга 6.2, для генерации новых страниц с использованием класса
Раде требуется выполнить совсем незначительный объем работы. Такое использова-
ние класса означает, что допустимо лишь незначительное отличие страниц друг от
друга.
Если нужно, чтобы в некоторых разделах сайта использовался вариант стандарт-
ной страницы, можно просто скопировать page, inc в новый файл раде2 . inc и вне-
сти в него некоторые изменения. Это значит, что при каждом обновлении или ис-
правлении в файле раде. inc нельзя забывать внести эти же изменения и в файл
раде2.inc.
Более рациональный подход предусматривает использование механизма наследо-
вания для создания нового класса, который наследует большую часть своих функцио-
нальных возможностей от класса Раде, но перекрывает те части, которые должны
отличаться.
Применительно к сайту компании TLA Consulting требуется, чтобы страница услуг
содержала вторую навигационную панель.
Сценарий, показанный в листинге 6.3, решает эту задачу путем создания нового
класса ServicesPage, унаследованного от класса Раде. В этом классе мы определяем
новый массив $row2buttons, который содержит кнопки и связи, необходимые во
второй строке. Поскольку мы хотим, чтобы этот класс в основном сохранил поведе-
ние родительского класса, мы перекрываем только ту часть, которая должна изме-
няться, а именно — операцию Display ().
Глава 6. Объектно-ориентированное программирование на РНР
203
Листинг 6.3. services -php — страница услуг унаследована от класса Раде,
но в ней перекрыта операция Display () для изменения вывода
<?php
require ('page.inc');
class ServicesPage extends Page
{
private $row2buttons = array('Реинжиниринг' => 'reengineering.php',
'Соответствие стандартам' => 'standards.php',
'Соответствие слоганам' => 'buzzword.php',
'Формулирование миссии' => 'mission.php'
) ;
function Display!)
{
echo "<html>\n<head>\n";
$this -> DisplayTitle();
$this -> DisplayKeywords();
$this -> Displaystyles();
echo "</head>\n<body>\n";
$this -> DisplayHeader();
$this -> DisplayMenu($this->buttons);
$this -> DisplayMenu($this->row2buttons);
echo $this->content;
$this -> DisplayFooter();
echo "</body>\n</html>\n";
}
}
$services = new ServicesPage!);
$services -> content = '<р>Компания TLA Consulting предлагает множество
услуг. Возможно, если мы выполним реинжиниринг вашего бизнеса,
производительность всех ваших работников возрастет.
Может быть, все, что вам нужно, - это построить
новую формулировку своей миссии либо новый пакет слоганов.</р>';
$services -> Display О;
Перекрытая операция Display!) очень похожа на операцию в родительском
классе, но содержит одну дополнительную строку:
$this -> DisplayMenu($this->row2buttons);
которая второй раз вызывает операцию DisplayMenu () и создает вторую панель меню.
За пределами определения класса мы создаем экземпляр класса ServicesPage, ус-
танавливаем значения, которые должны отличаться от значений по умолчанию, и
вызываем операцию Display!).
Как видно на рис. 6.2, мы получаем новый вариант стандартной страницы. При
этом нам пришлось создать код только для отличающихся частей страниц.
Создание страниц при помощи PHP-классов обладает очевидными преимущества-
ми. Учитывая, что класс выполняет большую часть действий, для создания новой
страницы приходится выполнять меньший объем работы. Мы можем обновлять сразу
все страницы, для этого достаточно обновить класс. Пользуясь механизмом наследо-
вания, из оригинала можно получать различные версии класса, сохраняя при этом
указанные выше преимущества.
204
Часть I. Использование РНР
Рис. 6.2. Страница услуг создается с использованием механизма
наследования, что позволяет повторно использовать большую
часть кода стандартной страницы
Однако, как это часто бывает в жизни, за упомянутые преимущества приходится
платить.
Для создания страниц из сценария требуется больше процессорных операций,
чем для простой загрузки статической HTML-страницы с диска и пересылки ее в
браузер. Для сайта с высоким трафиком это имеет большое значение, поскольку вам
придется либо использовать статические HTML-страницы, либо по возможности кэ-
шировать вывод сценариев, где только возможно, чтобы тем самым уменьшить на-
грузку на сервер.
Дополнительная объектно-
ориентированная функциональность РНР
В следующих разделах обсуждаются дополнительные объектно-ориентированные
возможности РНР, большая часть которых появилась в РНР5.
Сравнение РНР4 и РНР5
Если вы пользуетесь одной из предшествующих версий РНР, то должны прини-
мать во внимание множество важных отличий.
В РНР4 объекты передавались по значению, а сейчас они передаются по ссылке.
Это никоим образом не разрушает старые коды, и многие программисты зачастую
пишут неэффективный код, не давая себе отчет в этом. Например, следующий код:
$с = new myClass;
создает новый экземпляр класса и затем копирует его в $с (фактически создавая две
копии и тут же теряя дескриптор одной из них). Такое поведение может привести к
Глава 6. Объектно-ориентированное программирование на РНР
205
проблемам, если вы предполагаете, что объекты передаются по ссылке, в частности,
в функции.
Большинство объектно-ориентированных языков передают объекты по ссылке по
умолчанию, и РНР теперь попадает в эту категорию.
Другое существенное отличие заключается в том, что ранее в РНР были сложно-
сти с разыменованием объектов, возвращаемых из функций, обычно для вызова ме-
тодов этих объектов. Ранее, например, вы не могли записать следующий код:
select_object()->display();
в котором функция select_object() возвращала объект, имеющий метод с именем
display (). Теперь этот оператор работает без проблем.
Использование констант класса
В РНР5 внедрена идея констант класса. Константа класса может использоваться
без необходимости создания экземпляра класса, как показано в следующем примере:
<?php
class Math {
const pi = 3.14159;
)
echo 'Math::pi = '.Math::pi."\n";
?>
Доступ к константам класса осуществляется с помощью операции : : и указания
имени класса, которому константа принадлежит.
Реализация статических методов
В РНР5 также введено ключевое слово static. Оно применяется к методам, по-
зволяя им быть вызванными без необходимости создания экземпляра класса. Идея
статических методов подобна идее констант класса. Например, вернемся еще раз к
классу Math, который упоминался в предыдущем примере. Вы можете добавить к нему
статический метод squared () и вызывать его, не создавая экземпляр класса:
class Math
{
static function squared($input)
{
return $input*$input;
}
}
echo Math::squared(8);
Обратите внимание, что внутри статического метода нельзя использовать ключе-
вое слово this, поскольку может не существовать ни одного экземпляра для ссылки.
Проверка типа объекта и указание типов
Также новым в РНР5 является ключевое слово instanceof и концепция указания
типов (type hinting).
206
Часть I. Использование РНР
Ключевое слово instanceof позволяет проверить тип объекта. Вы можете прове-
рить, является ли объект экземпляром заданного класса, унаследован ли он от опре-
деленного класса либо реализует ли он некоторый интерфейс. Это ключевое слово
фактически представляет собой условную операцию. Принимая во внимание преды-
дущие примеры, в которых класс В был реализован как суб класс класса А, рассмотрим
следующие примеры использования instanceof:
($b instanceof В) в результате дает true.
($b instanceof А) в результате дает true.
($b instanceof Displayable) в результате дает false.
Во всех этих примерах предполагается, что А, В и Displayable находятся в теку-
щей области видимости; в противном случае возникнет ошибка.
Также новой идеей, внедренной в РНР5, является указание типов. Обычно при пе-
редаче параметра в PHP-функцию тип этого параметра не указывается. С помощью ука-
зания типов можно задать тип класса, который должен передаваться, и если тип фак-
тического параметра не совпадает с ним, генерируется ошибка. Проверка типов
эквивалентна instanceof. Например, предположим, что имеется следующая функция:
function check_hint(B Ssomeclass)
{
//.. .
)
В этой функции указано, что параметр $someclass должен быть экземпляром
класса В. Если передать в функцию экземпляр класса А, то есть:
check_hint($а) ;
будет получена следующая фатальная ошибка:
Fatal error: Argument 1 must be an instance of В
Фатальная ошибка: Аргумент 1 должен быть экземпляром В
Следует отметить, что если указать для параметра тип класса А и затем передать в
функцию экземпляр класса В, ошибка не возникнет, поскольку' В унаследован от А.
Клонирование объектов
В РНР5 появилось новое ключевое слово clone, которое позволяет копировать
существующие объекты. Например:
$с = clone $b;
создать копию объекта $Ь того же самого класса с теми же самыми значениями атри-
бутов.
Вы можете изменить данное поведение. Если вам необходимо нестандартное по-
ведение clone, создайте в базовом классе метод с именем_clone (). Этот метод по-
хож на конструктор или деструктор и не вызывается напрямую. Он вызывается, когда
используется ключевое слово clone, как показано выше. Внутри метода_clone ()
можно определить требуемые действия по копированию.
Очень важное свойство__clone () состоит в том, что этот метод вызывается по-
сле создания точной копии стандартным способом, то есть на данном этапе можно
изменять только то, что требуется.
Глава 6. Объектно-ориентированное программирование на РНР
207
Чаще всего в__clone () добавляется функциональность, которая проверяет кор-
ректность копирования атрибутов класса и ссылок. Если вы создаете копию класса,
содержащего ссылку на какой-то объект, и требуется, чтобы этот объект также был
скопирован, данные действия должны быть добавлены в_clone ().
Иногда необходимо выполнить какие-то другие действия, например, обновить ба-
зу данных на основе информации, хранящейся в экземпляре класса.
Использование абстрактных классов
Очередным нововведением в РНР5 следует считать абстрактные классы. Для та-
ких классов никогда не создаются экземпляры.
В РНР5 также доступны абстрактные методы, которые обеспечивают только сиг-
натуру метода, без его реализации, как показано ниже:
abstract operationX($paraml, $param2);
Любой класс, содержащий абстрактные методы, должен быть абстрактным:
abstract class А
{
abstract function operationX($paraml, $param2);
}
Основное применение абстрактных методов и классов связано с построением
сложной иерархии классов, в которой каждый субкласс должен содержать и перекры-
вать определенные методы; этого же можно достигнуть и посредством интерфейсов.
Перегрузка методов с помощью___________________call ()
Ранее мы сталкивались с несколькими методами класса специального назначения,
имена которых начинались с двух подчеркиваний (_), наподобие__get (),_set (),
__construct () и_destruct (). Еще одним примером такого метода может служить
_________________call (), который используется в РНР для перегрузки методов.
Перегрузка методов характерна для многих объектно-ориентированных языков,
однако она не настоль полезна в РНР, так как в основном здесь применяются гибкие
типы и простые в реализации необязательные функциональные параметры.
Для использования перегрузки функций потребуется реализовать метод_call (),
как показано в примере ниже:
public function__call($method, $p)
{
if ($method == 'display')
if (is_object($p[0]))
$this->displayObject($p[0]);
else if (is_array($p[O]))
$this->displayArray($p[O]);
else
$this->displayScalar($p[OJ) ;
}
Метод___call () принимает два параметра. Первый параметр содержит имя вызы-
ваемого метода, а второй — массив параметров, передаваемых этому методу. В теле
__call () принимается решение о том, какой конкретно метод из числа лежащих в
208
Часть I. Использование РНР
основе переданного должен быть вызван. В данном случае, если методу displayO
должен быть передан объект, вызывается лежащий в основе метод displayobject (),
если должен быть передан массив — метод displayArray (), а во всех остальных слу-
чаях — метод displayScalar ().
Для вызова показанного выше кода сначала потребуется создать экземпляр класс,
содержащего метод___call () (пусть он называется overload), а затем обратиться к
методу display ():
$ov = new overload;
$ov->display(array(1, 2, 3));
$ov->display('cat1);
Первый вызов displayO приведет к выполнению displayArray(), а второй вы-
зов — к выполнению displayScalar ().
Для того чтобы этот код работал, вам не нужна ни одна лежащая в основе реализа-
ция display().
Использование___________autoload ()
Следующей специальной функцией является__autoload(). Это не метод класса, а
стандартная функция, которая должна объявляться за пределами любых объявлений
классов. Если ее реализовать, она будет автоматически вызываться при попытке соз-
дания экземпляра необъявленного класса.
Основное назначение__autoload () состоит во включении всех файлов, необхо-
димых для создания экземпляров требуемого класса. Рассмотрим следующий пример:
function _autoload($name)
{
include_once $name.’.php';
}
Эта реализация предпринимает попытку включить файл с именем, совпадающим с
именем класса.
Реализация итераторов и итерации
Исключительно полезная характеристика нового объектно-ориентированного
механизма связана с возможностью использования цикла foreach() для выполнения
итерации по атрибутам объекта подобно тому, как это делается в отношении элемен-
тов массива. Ниже показан пример:
class myClass
{
public $а=5;
public $b=7;
public $c=9;
}
$x = new myClass;
foreach ($x as $attribute)
echo $attribute.'<br />';
(На момент написания этой книги официальное руководство по РНР советовало
реализовать пустой интерфейс Traversable, чтобы интерфейс foreach нормально
Глава 6. Объектно-ориентированное программирование на РНР
209
функционировал, однако если поступить таким образом, возникает фатальная ошиб-
ка. С другой стороны, без Traversable все работает нормально.)
Если требуется получить более сложное поведение, необходимо реализовать ите-
ратор. Для этого создайте класс, для которого должна выполняться итерация через
реализацию интерфейса IteratorAggregate, и напишите метод getlterator, воз-
вращающий экземпляр класса итератора. Класс должен реализовывать интерфейс
Iterator, содержащий набор методов, которые также должны быть реализованы.
Пример реализации класса и итератора показан в листинге 6.4.
Листинг 6.4. iterator .php — пример базового класса и класса итератора
<?php
class Objectiterator implements Iterator {
private $obj;
private $count;
private $currentlndex;
function __construct($obj)
(
$this->obj = $obj;
$this->count = count($this->obj->data);
)
function rewind()
{
$this->currentlndex = 0;
}
function validO
{
return $this->currentlndex < $this->count;
}
function key()
{
return $this->currentlndex;
}
function current()
{
return $this->obj->data[$this->currentlndex];
}
function next()
(
$ thi s-> currentIndex+ +;
}
class Object implements IteratorAggregate
{
public $data = arrayO;
function ___construct($in)
{
$this->data = $in;
}
210
Часть l. Использование PHP
function getlterator()
{
return new Objectiterator($this);
}
}
$myObject = new Object(array(2, 4, 6, 8, 10));
$mylterator = $myObject->getIterator();
for($mylterator->rewind(); $mylterator->valid(); $mylterator->next())
{
$key = $mylterator->key();
$value = $mylterator->current();
echo "$key => $value <br />”;
}
Класс Object Iterator имеет набор функций, как того требует интерфейс Iterator:
Конструктор не требуется, однако, очевидно, он представляет собой хорошее
место для установки значений итерируемых элементов и ссылки на текущий
элемент.
Функция rewind () должна устанавливать внутренний указатель данных на на-
чало данных.
Функция validf) должна сообщать, существуют ли дополнительные данные в
текущей позиции указателя.
Функция key () должна возвращать значение указателя данных.
Функция value () должна возвращать значение, хранящееся по указателю данных.
Функция next () должна перемещать указатель данных внутри данных.
Причина использования класса такого итератора состоит в том, что интерфейс к
данным не изменяется, даже если изменяется представление данных. В рассмотрен-
ном примере класс IteratorAggregate представляет собой простой массив. Даже
если вы решите изменить его, скажем, на хеш-таблицу или односвязный список, вы
все равно сможете пользоваться стандартным интерфейсом Iterator для обхода его,
несмотря на то что код реализации интерфейса Iterator изменится.
Преобразование классов в строки
Еще одной новой “магической” функцией является_____toStringO. Если реализо-
вать в классе функцию _____________________________toStringO, она будет вызываться при попытке вывода
класса на печать, как показано ниже:
$р = new Printable;
echo $р;
Все, что возвращает функция _toStringO, посредством echo будет выводиться
на экран. Эту функцию можно реализовать, например, следующим образом:
class Printable
{
var $testone;
Глава 6. Объектно-ориентированное программирование на РНР
211
var $testtwo;
public function _toStringO
{
return(var_export($this, TRUE));
}
}
(Функция var_export () выводит на печать значения всех атрибутов класса.)
Использование Reflection API
Очень удачной новой возможностью РНР5 следует считать API-интерфейс отра-
жения Reflection API. Отражение — это способность запрашиваемых существующих
классов и объектов сообщать информацию о своих структурах и содержимом. Данная
возможность исключительно полезна при взаимодействии с неизвестными или не-
документированными классами, которые представлены в закодированных РНР-
сценариях.
Этот API-интерфейс чрезвычайно сложен, тем не менее, давайте рассмотрим про-
стой пример, который, возможно, позволит ухватить основную идею. В примере ис-
пользуется ранее определенный в этой главе класс Раде. Получить всю информацию
о классе Раде с помощью Reflection API можно так, как показано в листинге 6.5.
Листинг 6.5. ref lection.php — отображает информацию о классе Раде
<?php
require_once('page.inc');
$class = new Reflectionclass('Page');
echo '<pre>';
echo $class;
echo '</pre>';
?>
Здесь для вывода на печать информации используется метод_toString () класса
Reflection. Обратите внимание на дескрипторы <рге>, которые не имеют отноше-
ния к информации, выдаваемой методом_toStr ing ().
Один из экранов вывода, сгенерированного приведенным выше кодом, показан на
рис. 6.3.
Что дальше
В следующей главе описаны новые возможности обработки исключений в РНР.
Исключения предоставляют элегантный механизм для разрешения ошибок, возни-
кающих во время выполнения.
212
Часть I. Использование РНР
Рис. 6.3. Вывод, обеспечиваемый Reflection API, на удивление
подробный
Глава 6. Объектно-ориентированное программирование на РНР
213
7
Обработка
исключений
В этой главе рассмотрены концепции обработки исключений и описан способ
реализации этой обработки в РНР. Исключения — это новые и важные компо-
ненты РНР5. Они предоставляют унифицированный механизм расширяемой, удоб-
ной для обслуживания и объектно-ориентированной обработки ошибок.
В главе, помимо прочих, рассматриваются следующие темы:
Концепции обработки исключений.
Структуры управления исключениями: try.. . throw.. .catch.
Класс Exception.
Исключения, определяемые пользователем.
Исключения в приложении “Автозапчасти от Боба”.
Исключения и другие механизмы обработки ошибок РНР.
Концепции обработки ошибок
Основная идея обработки ошибок состоит в выполнении кода внутри так назы-
ваемого блока try. Этот блок представляет собой раздел кода следующего вида:
try
{
// здесь находится необходимый код
}
В случае возникновения непредвиденных ситуаций внутри блока try можно вы-
полнить так называемую генерацию (throwing) исключения. Некоторые языки, такие как
Java, в определенных случаях генерируют исключения автоматически. В РНР исклю-
чения нужно генерировать вручную. Это выполняется следующим образом:
throw new Exception('сообщение', код);
Ключевое слово throw включает механизм обработки исключения. Приведенный
фрагмент скорее представляет собой конструкцию языка, а не функцию, но ему необ-
ходимо передать значение. Эта конструкция ожидает получения объекта. В простей-
шем случае ее можно использовать для создания экземпляра встроенного класса
Exception, как и было сделано в приведенном примере.
Конструктор этого класса принимает два параметра: сообщение и код. Они служат
для представления сообщения об ошибке и номера ошибки. Оба эти параметра не-
обязательны.
И, наконец, за блоком try должен следовать как минимум один блок catch, кото-
рый выгладит подобно показанному ниже:
catch (указание_типа исключение)
{
// обработка исключения
}
С одним блоком try может быть связано несколько блоков catch. Использование
нескольких блоков catch имеет смысл, если каждый из них ожидает перехвата от-
дельного типа исключения. Например, если требуется перехватывать исключения
класса Exception, блок catch может выглядеть следующим образом:
catch (Exception $е)
(
// обработка исключения
}
Объект, передаваемый в блок catch (и перехватываемый им), является тем, кото-
рый передается (и генерируется) оператором throw, генерирующим исключение.
Исключение может быть любого типа, однако удобнее всего использовать либо класс
Exception, либо экземпляры собственных пользовательских исключений, унаследо-
ванных от класса Exception. (Определение пользовательских исключений рассмат-
ривается далее в этой главе.)
В случае возникновении исключения код РНР ищет соответствующий блок catch.
При наличии более одного блока catch передаваемые в них объекты должны иметь
различные типы, чтобы РНР мог определить, какой именно блок catch соответствует
конкретном}’ случаю.
Еще один заслуживающий внимания момент связан с возможностью генерирова-
ния дополнительных исключений внутри блок catch.
Для большей наглядности рассмотрим пример. Простой пример обработки ис-
ключения показан в листинге 7.1.
Листинг 7.1. basic exception.php — генерирование и перехват исключения
<?php
try
{
throw new Exception('Возникла очень серьезная ошибка', 42);
}
catch (Exception $е)
{
echo 'Исключение '. $e->getCode(). ': '. $e->getMessage()
.' в '. $e->getFile(). ' , строка '. $e->getLine(). '<br />';
}
?>
Глава 7. Обработка исключений
215
В листинге 7.1 видно, что мы воспользовались несколькими методами класса
Exception, которые будут рассмотрены несколько позже. Результат выполнения
этого кода показан на рис. 7.1.
Рис. 7.1. Этот блок catch выводит сообщение об ошибке с указани-
ем места ее возникновения
В приведенном примере кода было сгенерировано исключение класса Exception.
Методы этого встроенного класса можно использовать в блоке catch для вывода по-
лезного сообщения об ошибке.
Класс Exception
РНР5 поставляется со встроенным классом Exception. Как уже упоминалось, кон-
структор этого класса принимает два параметра: сообщение об ошибке и номер
ошибки.
Кроме конструктора, этот класс содержит следующие встроенные методы:
getCode () — возвращает код, переданный конструктору.
getMessage () — возвращает сообщение, переданное конструктору.
getFile() — возвращает полный путь файла кода, в котором возникло исклю-
чение.
getLine () — возвращает количество строк в файле кода, в котором возникло
исключение.
getTrace () — возвращает массив, содержащий информацию отслеживания места
возникновения исключения.
getTraceAsString ()— возвращает ту же информацию, что и метод getTrace,
но сформатированную в виде строки.
toStringO — позволяет упростить вывод с помощью echo объекта Exception,
предоставляя всю информацию, полученную из перечисленных методов.
Как видите, в коде листинга 7.1 были использованы четыре из этих методов. Эту
же информацию (плюс информацию отслеживания) можно было бы получить с по-
мощью следующего оператора:
echo $е;
216
Часть I. Использование РНР
Информация отслеживания (backtrace) указывает, какие функции выполнялись в
момент возникновения исключения.
Исключения, определяемые пользователем
Вместо создания и передачи экземпляра базового класса Exception можно пере-
давать любой другой объект. В большинстве случаев вы будете расширять класс
Exception для создания своих собственных классов исключений.
Конструкция throw позволяет передавать любые другие объекты. Иногда такая
потребность может возникать при наличии проблем, связанных с каким-то конкрет-
ным объектом, и необходимости его передачи в целях отладки.
Как уже говорилось, в большинстве случаев приходится расширять базовый класс
Exception. В руководстве по РНР показан код, который демонстрирует скелет класса
Exception. Этот код, доступный по адресу http: //www.php.net/zend-engine-2.php,
воспроизведен в листинге 7.2. Обратите внимание, что это не реальный код, а лишь
те компоненты, которые можно наследовать.
Листинг 7.2. Класс Exception — компоненты класса, которые можно наследовать
<?php
class Exception {
function __construct(string $message=NULL, int $code=0) {
i f (func_num_args()) {
$this->message = $message;
}
$this->code = $code;
$this->file = FILE ; // из конструкции throw
$this->line = LINE ; // из конструкции throw
$this->trace = debug_backtrace();
$this->string = StringFormat($this);
}
protected $message = 'Unknown exception'; // сообщение исключения
protected $code =0; // определяемый пользователем код исключения
protected $file; // исходное имя файла исключения
protected $line; // исходная строка исключения
private $trace; // информация отслеживания
private $string; // только для внутреннего пользования!!
final function getMessage(){
return $this->message;
}
final function getCodeO {
return $this->code;
}
final function getFileO (
return $this->file;
}
final function getTrace() {
return $this->trace;
}
Глава? Обработка исключений
217
final function getTraceAsStringO {
return self::TraceFormat($this);
}
function _toString() {
return $this->string;
}
static private function StringFormat(Exception $exception) {
// ... недоступная в PHP-сценариях функция,
// которая возвращает всю необходимую информацию в виде строки
}
static private function TraceFormat(Exception $exception) {
// ... недоступная в PHP-сценариях функция,
// которая возвращает всю информацию отслеживания в виде строки
}
}
? >
Основная причина, по которой было приведено определение этого класса, состо-
ит в том, что большинство общедоступных методов являются финальными: то есть,
их нельзя перекрыть. Можно создать собственный субкласс Exceptions, но нельзя
менять поведение базовых методов. Вместе с тем, можно перекрывать функцию
__toStringO, чтобы изменять способ отображения исключения. Можно также до-
бавлять собственные методы.
Пример определенного пользователем класса Exception показан в листинге 7.3.
Листинг 7.3. user_def ined_exception.php — пример определяемого
пользователем класса Exception
<?php
class myException extends Exception
{
function __toStringO
{
return 'ctable border><tr><td><strong>MCKnjo4eHne '. $this->getCode ()
. '</strong>: '. $this->getMessage().'<br />'.' в ’
. $this->getFile(). ', строка'. $this->getLine()
. ' c/tdx/trx/tablexbr />' ;
}
}
try
{
throw new myException('Произошла очень серьезная ошибка', 42);
}
catch (myException $m)
{
echo $m;
}
?>
В этом коде объявляется новый класс исключения с именем myException, расши-
ряющий базовый класс Exception. Различие между этим классом и классом Exception
218
Часть I. Использование РНР
связано с заменой метода__toString () для обеспечения более “изящного” вывода
сообщения об исключении. Результат выполнения этого кода показан на рис. 7.2.
Рис. 7.2. Класс myException обеспечивает “изящный вывод” со-
общений об исключениях
Приведенный пример очень прост. В следующем разделе мы рассмотрим способы
создания различных исключений, связанных с различными категориями ошибок.
Исключения в приложении
“Автозапчасти от Боба”
В главе 2 было описано, как данные заказа Боба можно сохранять в двумерном
(плоском) файле. Известно, что файловый ввод/вывод (фактически, любой вид вво-
да/ вывода) — это та область программы, в которой часто возникают ошибки. Поэто-
му имеет смысл применить к нему механизм обработки исключений.
Если вернуться к исходному коду, можно заметить, что в процессе записи в файл
могут возникнуть три проблемы: невозможность открытия файла, невозможность
получения блокировки или невозможность записи в файл. Для каждой из этих ситуа-
ций мы создали класс исключения. Код этих классов исключений представлен в лис-
тинге 7.4.
Листинг 7.4. f ile exceptions .php—исключения, связанные с файловым вводом/выводом
<?php
class fileOpenException extends Exception
{
function _toString!)
{
return 'fileOpenException '. $this->getCode()
. $this->getMessage0.'<br />'.' в '
. $this->getFile(). ', строка'. $this->getLine()
. '<br />':
}
}
Глава 7. Обработка исключений
219
class fileWriteException extends Exception
{
function __toStringO
(
return 'fileWriteException $this->getCode()
. $this->getMessage().'<br />'.' в '
. $this->getFile(). строка'. $this->getLine()
. '<br />';
}
}
class fileLockException extends Exception
{
function __toStringO
{
return 'fileLockException '. $this->getCode()
. ': '. $this->getMessage().'<br />'.' в '
. $this->getFile(). ' , строка $this->getLine()
. '<br />';
}
}
Эти субклассы Exception не выполняют никаких действий, представляющих осо-
бый интерес. Фактически в этом приложении можно было бы оставить их пустыми
или воспользоваться базовым классом Exception. Тем не менее, мы включили в каж-
дый субкласс метод____toStringO, который выводит сообщение о типе возникшего
исключения.
Чтобы внедрить обработку исключений, мы переписали файл processorder .php,
рассмотренный в главе 2. Новая версия этого файла показана в листинге 7.5.
Листинг 7.5. processorder .php — сценарий обработки заказов Боба с добавленной
в него обработкой исключений
<?php
require_once('file_exceptions.php');
// создать короткие имена переменных
$tireqty = $_POST['tireqty'];
Soilqty = $_POST['oilqty'];
Ssparkqty = $_POST['sparkqty'];
Saddress = $_POST['address'];
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
<html>
<head>
<title>ABT03art4ac™ от Боба - Результаты 3aKa3a</title>
</head>
<body>
<Ы>Автозапчасти от Bo6a</hl>
<Ь2>Результаты заказа</Ь2>
<?php
$date = date('H:i, jS F');
220
Часть I. Использование PHP
echo '<р>3аказ обработан в ';
echo Sdate;
echo '</p>';
echo '<р>Список вашего заказа: </p>';
Stotalqcy = 0;
Stotalqty = Stireqty + $oilqty + Ssparkqty;
echo 'Заказано товаров: ’.$totalqty.'<br />';
iff $totalqty == 0)
{
echo 'Вы ничего не заказали на предыдущей странице!<br />';
}
else
{
if ( $tireqty>0 )
echo Stireqty.' автопокрышек<Ьг />';
if ( $oilqty>0 )
echo Soilqty.' бутылок с маслом<Ьг />';
if ( $sparkqty>0 )
echo $ sparkqty. ’ свечей зажиганижЬг />';
}
Stotalamount = 0.00;
define('TIREPRICE', 100);
define('OILPRICE' , 10);
define('SPARKPRICE' , 4) ;
Stotalamount = $tireqty * TIREPRICE
+ $oilqty * OILPRICE
+ Ssparkqty * SPARKPRICE;
$totalamount=number_format($totalamount, 2, ' ');
echo '<р>Итого по заказу: '.Stotalamount.'</p>';
echo '<р>Адрес доставки: ’.Saddress.’</p>';
Soutputstring = Sdate."\t"-Stireqty." автопокрышек^’.$oilqty." бутылок с масломХб"
•Ssparkqty." свечей зажигания\1\$”.Stotalamount
. "\t". Saddress."\n";
// открыть файл для добавления
try
{
if (!($fp= @fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'ab’)))
throw new fileOpenException();
if (!flock($fp, LOCK_EX))
throw new fileLockException();
if (!fwrite($fp, Soutputstring, strlen(Soutputstring)))
throw new fileWriteException();
flockfSfp, LOCK_UN);
fclose($fp);
echo '<р>3аказ записан.</p>' ;
}
catch (fileOpenException $foe)
Глава 7. Обработка исключений
221
{
echo 1<р><зЪгопд>Невозможно открыть файл заказов. '
.'Пожалуйста, обратитесь к Web-мастеру.</strongx/p>1;
}
catch (Exception $е)
{
echo ' <pxstrong>B настоящий момент ваш запрос не может быть обработан.
.'Пожалуйста, попытайтесь позже.</strongx/p>';
}
</body>
</html>
Как видите, раздел кода сценария, реализующий файловый ввод/вывод, помещен
в блок try. В общем случае использование небольших блоков try, в конце которых
выполняется перехват важных исключений, считается правильным подходом к про-
граммированию. Это упрощает написание и сопровождение кода обработки исклю-
чений, поскольку легко понять, с чем приходится иметь дело.
В случае невозможности открытия файла код генерирует исключение fileOpen-
Exception; невозможность блокирования файла ведет к генерированию исключения
f ileLockException, а невозможность записи в файл — к генерированию исключения
fileWriteException.
Взгляните на блоки catch. Для целей иллюстрации мы показали только два таких
блока: один для обработки объектов fileOpenException и второй для обработки объ-
ектов Exception. Поскольку остальные исключения наследуют свойства и методы
класса Exception, они будут перехватываться вторым блоком catch. Сопоставление
блоков catch выполняется в соответствии с теми же основными правилами, что и
применяемые в операторе instanceof. Это обстоятельство является веским основа-
нием для создания пользовательских классов исключений за счет расширения одного
единственного класса.
Важное предупреждение: при генерировании исключения, для которого соответ-
ствующий блок catch не был создан, РНР сообщит о фатальной ошибке.
Исключения и другие механизмы
обработки ошибок РНР
Помимо механизма обработки исключений, рассмотренного в этой главе, РНР
обладает развитой поддержкой обработки ошибок, которая будет описываться в гла-
ве 25. Важно отметить, что процесс генерирования и обработки исключений не
влияет и не мешает работе данного механизма обработки ошибок.
Обратите внимание, что в листинге 7.5 вызов метода fopen () предварен символом
операции подавления ошибки (@). В случае неудачного выполнения этого метода, РНР
генерирует предупреждение, вывод которого на экран или запись в журнал зависит от
настроек отчета об ошибках, которые определены в файле php.ini. Эти параметры
рассматриваются в главе 25, но следует знать, что названное предупреждение будет
генерироваться независимо от того, выполняется ли генерирование исключения.
222
Часть I. Использование РНР
Дополнительные источники информации
Поскольку обработка исключений — новая функция РНР, по этой теме написано не
очень много. Однако существует достаточно большой объем базовой информации
по обработке исключений. Компания Sun на своем сайте http://java.sun.com/
docs/books/tutorial/essential/exceptions/definition.html предлагает хорошее
учебное пособие по вопросу о том, что собой представляют исключения и почему
возникает необходимость в их использовании (разумеется, представленная в пособии
информация относится к языку Java).
Что дальше
Следующая часть этой книги посвящена MySQL. В ней поясняются создание и за-
полнение базы данных MySQL, а также технология ее связывания с РНР-сценариями,
что дает возможность получать доступ к базе данных из Web.
Глава 7. Обработка исключений
223
II
Использование MySQL
Глава 8. Проектирование баз данных для Web
Глава 9. Создание базы данных для Web
Глава 10. Работа с базой данных MySQL
Глава 11. Доступ к базе данных MySQL из Web
с помощью РНР
Глава 12. Дополнительные сведения по
администрированию MySQL
Глава 13. Дополнительные сведения по
программированию в MySQL
8
Проектирование баз
данных для Web
После ознакомления с основами РНР можно приступать рассмотрению процесса
интегрирования баз данных со сценариями, реализующими поведение сайта.
Как вы, вероятно, помните, в главе 2 были описаны преимущества использования
реляционных баз данных по сравнению с двумерными файлами. Основными среди
преимуществ можно считать следующие:
Системы управления реляционными базами данных (СУРБД) обеспечивают
более быстрый доступ к данным, чем двумерные файлы.
Посредством запросов из СУРБД легко извлекать наборы данных, соответст-
вующие определенным критериям.
СУРБД обладают встроенным механизмом для работы с параллельным досту-
пом, что позволяет программисту не беспокоиться об этом.
СУРБД обеспечивают произвольный доступ к данным.
СУРБД обладают встроенными системами управления полномочиями.
Если говорить более предметно, то использование реляционных баз данных по-
зволяет быстро и без особых усилий ответить на такие вопросы, как: “Где проживают
клиенты?”, “Какие товары продаются наиболее успешно?” или “Какая категория кли-
ентов приносит наибольшую прибыль?” Эта информация может способствовать
улучшению сайта, преследуя цель не только не потерять клиентов, ио и привлечь но-
вых. При этом ее очень трудно получить, имея дело с двумерными файлами.
В этой части книги речь пойдет о базах данных MySQL. Однако прежде чем под-
робнее рассмотреть ее особенности (что будет сделано в следующей главе), необхо-
димо прояснить некоторые вопросы:
Концепции и терминология реляционных баз данных.
Проектирование баз данных для Web.
Архитектура баз данных для Web.
В последующих главах освещены следующие темы:
В главе 9 описаны основные конфигурации, необходимые для подключения
вновь разработанной базы данных MySQL к Web. Вы изучите, как создавать
226
Часть II. Использование MySQL
пользователей, базы данных, таблицы и индексы, а также узнаете о механизмах
хранения MySQL.
В главе 10 поясняется, как из командной строки выполнять запросы к базе дан-
ных, а также как добавлять, обновлять и удалять записи.
В главе 11 рассмотрена технология объединения РНР и MySQL, что позволяет
использовать базу данных и осуществлять ее администрирование через Web-
интерфейс. Вы изучите два метода реализации этого: с помощью РНР-биб-
лиотеки MySQL и посредством уровня абстракции баз данных PEARzDB.
В главе 12 детально описано администрирование MySQL, включая систему при-
вилегий, безопасность и оптимизацию.
В главе 13 предложено подробное описание механизмов хранения, а также
транзакций, полнотекстового поиска и хранимых процедур.
Концепции реляционных баз данных
На сегодняшний день реляционные базы (БД) данных являются, пожалуй, наибо-
лее часто используемым типом БД. Они построены на основе строгих законов реля-
ционной алгебры. Чтобы пользоваться реляционными базами данных (а это весьма
неплохая идея'.), вовсе необязательно досконально разбираться в реляционной тео-
рии, однако все же следует овладеть основными понятиями о базах данных.
Таблицы
Реляционные базы данных построены на основе отношений, обычно называемых
таблицами. Таблица представляет собой именно то, что и подразумевает этот тер-
мин — таблицу с данными. Если вам когда-либо приходилось иметь дело с электрон-
ной таблицей, значит, вы уже имеете опыт использования реляционной таблицы.
Р ассмотрим пример таблицы, показанный на рис. 8.1. Эта таблица содержит име-
на и адреса клиентов книжного магазина “Буквофил” (“Book-O-Rama”).
Customers (Клиенты)
CustomerlD (Идентификатор клиента) Name (ФИО) Address (Адрес) City (Город)
1 Julie Smith 25 Oak Street Airport West
2 Alan Wong 1/47 Haines Avenue Box Hill
3 Michelle Arthur 357 North Road Varraville
Рис. 8.1. Таблица со сведениями о покупателях магазина “Буквофил”
Таблица имеет собственное имя — Customers (Клиенты), несколько столбцов, ка-
ждый из которых содержит определенного рода данные, а также строки, в которых
записаны сведения о клиентах.
Глава 8. Проектирование баз данных для Web
227
Столбцы
Каждый столбец в таблице имеет уникальное имя и содержит разнообразные дан-
ные. Каждому столбцу отвечает определенный тип данных. Например, в таблице
Customers, которая показана на рис. 8.1, можно видеть, что столбец CustomerlD
(Идентификатор клиента) хранит целочисленную информацию, а остальные три
столбца — строковую. Иногда на столбцы ссылаются также как на поля или атрибуты.
Строки
Каждая строка в таблице представляет отдельного клиента. Вследствие использо-
вания табличного формата все строки имеют одни и те же атрибуты. Строки также
называют записями или кортежами.
Значения
Каждая строка состоит из набора отдельных значений, соответствующих столб-
цам. Тип данных каждого значения должен соответствовать типу данных, заданному
столбцом.
Ключи
Клиентов нужно различать. Обычно имена не очень подходят для этого — если
ваше имя достаточно распространенное, думается, вполне понятно, почему так
сложно отличить одного Сидорова Ивана Петровича от полета других Сидоровых
Иванов Петровичей. Взять, например, Julie Smith из таблицы Customers. Если от-
крыть телефонную книгу, то окажется, что такое имя и фамилия встречаются в ней
слишком часто.
Существует несколько способов отличить незабвенную Julie Smith от других. На-
пример, вероятнее всего, она — единственная Julie Smith, проживающая по данному
адресу. Однако строка “Julie Smith, проживающая по адресу 25, Oak Street. Airport
West" слишком длинная и звучит чересчур официально. К тому же подобный способ
идентификации предполагает использование нескольких столбцов таблицы.
Мы поступили следующим образом (вероятнее всего, вы поступите так же) — каж-
дому клиенту' присвоили уникальный идентификатор клиента (Customer 12). При
этом используется тот же принцип, что и в банке, где счетам клиентов присваивают
уникальные номера, или в клубе, где каждому члену клуба выдают индивидуальную
членскую карточку с уникальным номером. Такой подход облегчает хранение сведе-
ний в базе данных, а искусственное присвоение идентификационного номера гаран-
тирует его уникальность. Лишь немногие реальные сведения о клиенте, даже при со-
вместном их использовании, обладают подобным свойством.
Столбец идентификации в таблице называется ключом (key) или первичным ключом
(primary key). Ключ может состоять из нескольких столбцов. Например, если бы мы
решили идeнтифициpoвaтьJulte Smith по строке “Julie Smith, of 25 Oak Street. .Airport
West", то ключ состоял бы из столбцов Name, Address и City. При этом нельзя было
бы гарантировать его уникальность.
Обычно базы данных состоят из нескольких таблиц, для которых ключ служит
связующим звеном. На рис. 8.2 показана база данных, в которую добавлена вторая
228
Часть II. Использование MySQL
таблица. В ней размещаются сведения о заказах, сделанных клиентами. Каждая стро-
ка в таблице Orders (Заказы) представляет один заказ, сделанный одним клиентом.
Клиента можно установить по хранящемуся в таблице идентификатору клиента Cus-
tomer ID. Например, если взглянуть на заказ с идентификатором заказа Order ID рав-
ным 2, видно, что его сделал клиент с CustomerlD равным 1. Затем, обратившись к
таблице Customers, можно выяснить, что CustomerID=l присвоен клиенту^Не Smith.
Customers (Клиенты)
CustomerlD (Идентифика- тор клиента) Name (ФИО) Address (Адрес) City (Город)
1 Julie Smith 25 Oak Street Airport West
2 Alan Wong 1/47 Haines Avenue Box Hill
3 Michelle Arthur 357 North Road Yarraville
Orders (Заказы)
OrderlD (Идентифика- тор заказа) CustomerlD (Идентифика- тор клиента) Amount (Сумма) Date (Дата)
1 3 27.50 02-Июн-2005
2 1 12.99 15-Июн-2005
3 2 74.00 19-Июн-2005
4 4 6.99 01-Июл-2005
Рис. 8.2. Каждый заказ в таблице Orders соответствует клиенту из
таблицы Customers
В соответствие с терминологией реляционных баз данных такая взаимосвязь на-
зывается внешним ключом (foreign key). CustomerlD — первичный ключ в таблице Cus-
tomers, однако когда он появляется в другой таблице, например, Orders, его называ-
ют внешним ключом.
Не исключено, что наше решение использовать две разные таблицы может вы-
звать у вас недоумение — почему бы просто не поместить адрес Julie Smith в таблицу
Orders? Подробнее это объясняется в следующем разделе.
Схемы
Полный набор эскизов таблиц базы данных называется схемой (schema) базы дан-
ных. В чем-то схема подобна чертежу. В ней должны быть изображены таблицы вме-
сте с их столбцами, а также указаны первичный ключ и все внешние ключи для каж-
дой таблицы. Схема не содержит никаких конкретных данных, однако в нее можно
поместить образцы данных, чтобы назначение тех или иных столбцов было понят-
нее. Схемы могут быть представлены в виде неформальных диаграмм, аналогичных
используемым в этой книге, в виде диаграмм “сущность-отношение” (которые не рас-
сматриваются в данной книге) или в текстовой форме наподобие следующей:
Customers(CustomerlD, Name, Address, City)
Orders(OrderlD, CustomerlD, Amount, Date)
Глава 8. Проектирование баз данных для Web
229
Подчеркнутые элементы в схеме — это первичные ключи того отношения (табли-
цы), где они подчеркнуты. Элементы, выделенные курсивом, представляют собой
внешние ключи соответствующего отношения.
Отношения
Внешние ключи представляют отношения между данными в двух таблицах. На-
пример, связь между таблицами Orders и Customers представляет отношение между
строкой в таблице Orders и строкой в таблице Customers.
В реляционной базе данных существуют три основных типа отношений. Их клас-
сифицируют в зависимости от количества элементов по каждую сторону отношения.
Различают отношения типа “один к одном}'”, "один ко многим”, "многие ко многим”.
Отношение “один к одному” означает, что с каждой стороны в отношении участ-
вует по одному элементу. Например, если бы адреса были помещены не в таблицу
Customers, а в какую-нибудь другую, между этими таблицами существовало бы отно-
шение “один к одному”. Таблицы Addresses (Адреса) и Customers могли бы быть свя-
заны внешним ключом либо как-то иначе (и то. и другое не обязательно).
При отношении “один ко многим” одна строка в первой таблице связана с не-
сколькими строками другой таблицы. В нашем примере один клиент может сделать
несколько заказов. В таких отношениях таблица, содержащая несколько строк, будет
иметь внешний ключ к таблице с одной строкой. В данном случае, чтобы проиллюст-
рировать это отношение, мы вставили идентификатор клиента в таблицу заказов.
В отношении типа “многие ко многим” несколько строк одной таблицы связаны с
несколькими строками другой. Например, при наличии двух таблиц Books (Книги) и
Authors (Авторы) могло бы выясниться, что одна книга была написана несколькими
авторами, каждый из которых написал и другие книги, причем некоторые из них
могли быть написаны опять-таки в соавторстве с другими. Как правило, такой тип
отношений полностью замыкает таблицу на саму себя. В результате могли бы сущест-
вовать таблицы Books, Authors и Books_Authors. Третья таблица содержала бы толь-
ко ключи из остальных двух таблиц в качестве парных внешних ключей, показываю-
щие, какие авторы принимали участие в написании той или иной книги.
Как спроектировать собственную базу
данных для Web
Знание того, когда требуется новая таблица и какой элемент нужно выбрать в ка-
честве ключа, относится, скорее, к искусству. Темам диаграмм "сущность-отношение”
и нормализации баз данных, которые выходят за рамки этой книги, посвящены бук-
вально горы научной литературы. Однако в большинстве случаев достаточно при-
держиваться нескольких основных принципов проектирования. Рассмотрим их при-
менительно к нашему проекту “Буквофил”.
Думайте о реальных объектах, которые вы моделируете
Как правило, при создании базы данных приходится моделировать объекты и взаи-
мосвязи реального мира и сохранять информацию об этих объектах и взаимосвязях.
230
Часть II. Использование MySQL
В общем случае каждый класс реальных моделируемых объектов нуждается в собст-
венной таблице. Подумайте о следующей ситуации: нам требуется хранить одинаковую
информацию обо всех наших клиентах. Но при существовании набора данных одинако-
вой “формы” мы запросто можем создать таблицу, соответствующую этим данным.
В примере с магазином “Буквофил” необходимо хранить сведения о клиентах,
продаваемых книгах и деталях заказов. У каждого клиента есть имя-фамилия и адрес.
Заказы отличаются датой оформления, общей стоимостью и списком заказанных
книг. Книги отличаются номером ISBN, автором, названием и ценой.
Исходя из этого, база данных должна содержать, как минимум, три таблицы:
Customers (Клиенты), Orders (Заказы) и Books (Книги). Исходная схема представлена
на рис. 8.3.
Customers (Клиенты)
CustomerlD (Идентифика- тор клиента) Name (ФИО) Address (Адрес) City (Город)
1 Julie Smith 25 Oak Street Airport West
2 Alan Wong 1/47 Haines Avenue Box Hill
3 Michelle Arthur 357 North Road Yarra ville
Orders (Заказы)
OrderlD (Идентифика- тор заказа) CustomerlD (Идентифика- тор клиента) Amount (Сумма) Date (Дата)
1 3 27.50 02-ИЮН-2005
2 1 12.99 15-ИЮН-2005
ly 2 74.00 19-ИЮН-2005
4 £_ 6.99 01-ИЮЛ-2005
Books(Книги)
ISBN Author (Автор) Title (Название) Price (Цена)
0-672-31687-8 Michael Morgan Java 2 for Professional Developers 34.99
0-672-31745-1 Thomas Down Installing Debian GNU/Linux 24.99
0-672-31509-2 Pruitt и др. Teach Yourself GIMP in 24 Hours 24.99
Рис. 8.3. Исходная схема состоит из таблиц Customers, Orders и Books
В данном случае, глядя на модель, нельзя узнать, какие книги были востребованы в
каждом заказе. Этим вопросом мы займемся несколько позже.
Избегайте хранения избыточной информации
Несколько ранее мы задавались вопросом: “Почет бы просто не хранить адрес
Julie Smith в таблице Orders?”
Глава 8. Проектирование баз данных для Web
231
Если Julie Smith закажет в магазине “Буквофил” несколько книг (на что мы ис-
кренне надеемся), сведения о ней придется записывать несколько раз. В итоге табли-
ца Orders может приобрести вид, показанный на рис. 8.4.
Orders (Заказы)
OrderlD (Идентифика- тор заказа) Amount (Сумма) Date (Дата) CustomerlD (Идентифика- тор клиента) ФИО (Имя) Address (Адрес) City (Город)
12 199,50 25-ИЮН-2005 1 Julie Smith 25 Oak Street Airport West
13 43,00 29-ИЮН-2005 1 Julie Smith 25 Oak Street Airport West
14 15,99 ЗО-Июн-2005 1 Julie Smith 25 Oak Street Airport West
15 23,75 01-ИЮЛ-2005 1 Julie Smith 25 Oak Street Airport West
Рис. 8.4. Проект базы данных, в которой хранится избыточная информация, занимает
больше места и может быть сопряжен с аномалиями в данных
С таким подходом связаны две основные проблемы.
Во-первых, имеет место напрасная трата пространства на жестком диске. Зачем
сохранять информацию о Julie Smith трижды, если достаточно сделать это только
один раз?
Во-вторых, в этом случае возможно возникновение аномалий обновления, то есть
ситуаций, когда обновление базы данных приводит к несоответствиям в данных. Це-
лостность данных нарушается, после чего неизвестно, какие данные корректны, а
какие — нет. Как правило, подобного рода ситуации оборачиваются потерей данных.
Следует избегать трех типов аномалий обновлений: аномалий модификации, ано-
малий ввода и аномалий удаления.
Если, ожидая выполнения заказа, Julie Smith переедет по другому адресу, новый
адрес придется указывать в трех местах, а не в одном, проделывая в три раза большую
работу. Можно подойти к этой проблеме и с другой стороны, изменив адрес всего в
одном месте, однако это приведет к еще худшим последствиям — несоответствию
данных в базе. Такие проблемы называют аномалиями модификации. поскольку они
появляются вследствие попыток модификации базы данных.
В этом случае сведения о Julie Smith придется вводить каждый раз, принимая за-
каз. Поэтому необходимо постоянно проверять соответствие между вводимыми све-
дениями и данными, записанными в таблице. В противном случае в таблице могут
появиться две строки с противоречащей друг другу информацией о Julie Smith. На-
пример, в одной строке может сообщаться, что Julie Smith живет в Airport West, а в
другой местом ее проживания будет указан Airport. Такое несоответствие называется
аномалией ввода, поскольку оно возникает во время ввода данных.
Третий тип аномалий называется аномалией удаления, поскольку проявляется (кто
бы мог подумать?) при удалении строк из базы данных. Для примера представим, что
после выполнения заказ удаляется из базы данных. То есть, как только все текущие
заказы Julie Smith выполнены, они удаляются из таблицы Orders. А это означает, что
мы больше не располагаем сведениями об адресе Julie Smith. Мы не сможем прислать
ей какие-то специальные предложения, и в следующий раз, когда она пожелает зака-
зать что-либо, придется снова собирать все данные о ней.
232
Часть II. Использование MySQL
Говоря в целом, проектирование базы данных следует выполнять так, чтобы пре-
дотвратить возникновение любой из описанных выше аномалий.
Используйте элементарные значения столбцов
Использование элементарных значений столбцов означает, что в каждом атрибу-
те каждой строки должен храниться только один элемент. Например, требуется уз-
нать, какие книги отобраны для каждого заказа. Этого можно достичь несколькими
путями. В таблицу Orders можно добавить столбец, в котором будет размешаться спи-
сок всех заказанных книг (см. рис. 8.5).
Orders (Заказы)
OrderlD (Идентифика- тор заказа) CustomerlD (Идентифика- тор клиента) Amount (Сумма) Date (Дата) Books Ordered (Заказанные книги)
1 3 27.50 02-ИЮН-2005 0-672-31697-8
2 1 12.99 15-Июн-2005 0-672-31745-1,0-672-31509-2
3 2 74.00 19-ИЮН-2005 0-672-31697-8
4 4 6.99 01-Июл-2005 0-672-31745-1,0-672-31509-2, 0-672-31697-8
Рис. 8.5. При таком подходе атрибут Books Ordered (Заказанные книги) каждой
строки содержит несколько значений
В силу ряда причин это не очень-то приемлемо. По существу в этом случае в один
столбец мы помещаем целую таблицу, связывающую заказы с книгами. Такой подход
осложняет ответ на вопрос типа: “Сколько экземпляров книги Java 2 for Professional
Developers’ было заказано?” Система не сможет просто подсчитать количество совпа-
дающих записей. Вместо этого ей придется проанализировать значение каждого ат-
рибута, чтобы найти внутри него любые возможные совпадения.
Поскольку в этом случае мы создаем таблицу в таблице, в действительности следу-
ет просто создать новую таблицу. Назовем ее Order_Iterns (Элементы_заказа). Схема
этой таблицы показана на рис. 8.6.
Orderjtems (Элементызаказа)
OrderlD (Идентифика- тор заказа) ISBN Quantity (Количество)
1 0-672-31697-8 1
2 0-672-31745-1 2
2 0-672-31509-2 1
3 0-672-31697-8 1
Рис. 8.6. Этот подход упрощает процесс поис-
ка заказанных книг
Глава 8. Проектирование баз данных для Web
233
Приведенная выше таблица обеспечивает связь между таблицами Orders п Books.
Использование таблиц подобного типа весьма характерно для случаев, когда два объ-
екта связаны между собой отношением “многие ко многим” — в данном случае один
заказ может включать в себя несколько книг, а каждая из книг может быть заказана
несколькими людьми.
Выбирайте подходящие ключи
Убедитесь в том, что выбранные ключи уникальны. В данном случае мы создали
специальные ключи для клиентов (CustomerlD) и для заказов (OrderlD), поскольку у
этих реальных объектов может не оказаться идентификатора, который гарантиро-
ванно является уникальным. Для книг создавать подобный идентификатор не требу-
ется. он у них уже есть — это номер ISBN. Для таблицы Order_Items можно, при же-
лании. добавить один ключ, однако комбинация атрибутов OrderlD и ISBN будет
уникальной до тех пор, пока заказ двух и более экземпляров одной и той же книги
рассматривается как одна строка. По этой причине в таблицу Order_Items включен
столбец Quantity.
Подумайте над вопросами, которые
потребуется задать базе данных
Продолжая цепочку рассуждений, подумайте, на какие вопросы желательно полу-
чить ответ от базы данных. (Вспомните, о чем говорилось в начале этой главы. На-
пример, какие книги магазина “Буквофил” продаются лучше других?) Убедитесь, что
база данных с одержит всю необходимую информацию, и что между таблицами уста-
новлены все нужные связи, чтобы можно было ответить на поставленные вопросы.
Избегайте проектов с большим
количеством пустых атрибутов
Если возникнет необходимость добавить в базу данных рецензии на книги, то су-
ществует, по меньшей мере, два варианта, как это сделать. Оба они продемонстри-
рованы на рис. 8.7.
Books(Книги)
ISBN Author (Автор) Title (Название) Price (Цена) Review (Рецензия)
0-672-31687-8 Michael Morgan Java 2 for Professional Developers 34.99
0-672-31745-1 Thomas Down Installing Debian GNU/Linux 24.99
0-672-31509-2 Pruitt и др. Teach Yourself GIMP in 24 Hours 24.99
Book Reviews (Рецензии на книги)
ISBN Review (Рецензия)
Рис. 8.7. Для того чтобы добавить рецензии, можно либо добавить в таблицу Books
столбец Review, либо создать специальную таблицу для рецензий
234
Часть II. Использование MySQL
Первый вариант подразумевает добавление столбца Review в таблицу Books. В та-
ком случае каждую книгу будет сопровождать поле с рецензией. Если в базе данных
много книг и рецензент не собирается делать обзор их всех, во многих строках этот
атрибут не будет иметь значения (или, как говорят, будет иметь нулевое значение).
Наличие большого количества пулевых значений в базе данных — плохая практи-
ка. Это влечет за собой нецелесообразное использование места на жестком диске,
проблемы с подсчитыванием итоговых сумм и выполнением других функций над чи-
словыми столбцами. Когда пользователь встречает в таблице нулевое значение, он не
знает, является ли данный атрибут незначащим, присутствует ли в базе данных ошиб-
ка, либо данные просто еще не введены.
Большинства проблем с нулевыми значениями можно избежать, воспользовав-
шись иным подходом к проектированию. Для этого можно использовать второй ва-
риант, представленный на рис. 8.7. Здесь в таблице Book_Reviews перечисляются
только книги и приводятся их рецензии.
Обратите внимание, что в основе этого подхода лежит идея рецензирования книг
единым рецензентом, то есть, между таблицами книг и рецензий существует отноше-
ние “один к одному’’’ Если вы хотите поддерживать несколько рецензий для одной и
той же книги, возникнет отношение “один ко многим”, и в качестве начального про-
екта необходимо выбрать второй вариант. Кроме того, если для книги поддержива-
ется одна рецензия, можно использовать ISBN в качестве первичного ключа в табли-
це Book_Reviews. При наличии многих рецензий для каждой книги необходимо
ввести уникальный идентификатор для каждой рецензии.
Типы таблиц
Как правило, базы данных состоят из двух типов таблиц:
Простые таблицы, описывающие реальные объекты. Они могут иметь ключи к
другим простым объектам, которые поддерживают отношения типа “один к
одному” и “один ко многим”. Например, один клиент может сделать несколько
заказов, однако в то же время один заказ размещается одним клиентом. Поэто-
му в заказе помещается ссылка на клиента.
Связывающие таблицы, которые описывают отношения типа “многие ко мно-
гим” между двумя реальными объектами, например, отношение между табли-
цами Orders и Books. Такие таблицы часто ассоциируются с некоторой реаль-
ной транзакцией.
Архитектура баз данных для Web
Теперь, когда рассмотрена внутренняя архитектура базы данных, пришло время
взглянуть на внешнюю архитектуры системы баз данных для Web и рассмотреть ме-
тодологию ее разработки.
Архитектура
Основная работа Web-сервера проиллюстрирована на рис. 8.8. Эта система состо-
ит из двух объектов: Web-браузера и Web-сервера. Между ними должен существовать
канал связи. Web-браузер посылает запрос на сервер, сервер отправляет' обратно от-
Глава 8. Проектирование баз данных для Web
235
вет. Такая архитектура подходит для сервера, отсылающего обычные статические
страницы. Архитектура же сайта, который включает в себя базу данных, несколько
сложнее.
Рис. 8.8. Отношение типа кли-
ент-сервер между Web-браузером
и Web-сервером требует наличия
канала обмена информацией
Приложения баз данных для Web, которые будут разрабатываться на страницах
этой книги, соответствуют общей структуре баз данных для Web, показанной на рис.
8.9. Большая часть этой структуры должна быть вам знакома.
Рис. 8.9. Базовая архитектура баз данных для Web включает в себя Web-
браузер, Web-сервер, сценарный механизм и сервер баз данных
Типичная транзакция базы данных для Web состоит из этапов, обозначенных на
рис. 8.9 цифрами. Мы рассмотрим их на примере магазина “Буквофил”.
1. Web-браузер пользователя отправляет HTTP-запрос определенной Web-стра-
ницы. Например, может быть выдан запрос на поиск в магазине “Буквофил”
всех книг, написанных Лорой Томсон (Laura Thomson), с использованием
HTML-формы. Страница с результатами поиска называется results .php.
2. Web-сервер принимает запрос на results.php, извлекает файл и передает его
на обработку механизму РНР.
3. Механизм РНР начинает синтаксический анализ сценария. Сценарий содер-
жит команду подключения к базе данных и выполнения запроса (на поиск
книг). РНР открывает соединение с сервером MySQL и отправляет ему соот-
ветствующий запрос.
4. Сервер MySQL принимает запрос базы данных, обрабатывает его. а затем от-
правляет результаты — в данном случае, список книг — обратно механизм}' РНР.
5. Механизм РНР завершает выполнение сценария, что обычно сопряжено с
форматированием результатов запроса в виде HTML, после чего возвращает
результаты в HTML-формате Web-серверу.
6. Web-сервер пересылает браузеру HTML-страницу, в которой пользователь мо-
жет просмотреть список необходимых книг.
В основном процесс остается неизменным независимо от используемого сценар-
ного механизма и сервера баз данных. Зачастую программное обеспечение Web-
сервера, механизм РНР и сервер баз функционируют на одном компьютере. В то же
время, достаточно часто сервер базы данных работает на другом компьютере. Это
236
Часть II. Использование MySQL
может быть обусловлено соображениями безопасности, увеличения пропускной спо-
собности или более эффективного распределения нагрузки. С точки зрения разра-
ботки этот подход не имеет особого значения, однако в плане производительности
второй вариант может оказаться более предпочтительным.
По мере возрастания размеров и сложности приложения, можно разделить РНР-
приложение на уровни — в общем случае, на уровень работы с базами данных, кото-
рый взаимодействует с MySQL, уровень реализации бизнес-логики, который содер-
жит ключевую часть приложения, и представительский уровень, управляющий
HTML-выводом. Тем не менее, базовая архитектура, показанная на рис. 8.9, по-
прежнему сохраняется: просто к раздел}', относящемуся к РНР, добавляются допол-
нительные структуры.
Дополнительная информация
В данной главе были раскрыты некоторые принципы проектирования реляцион-
ных баз данных. Если вы желаете подробнее ознакомиться с теорией реляционных
баз данных, можете обратиться к книгам таких признанных специалистов по реляци-
онным базам данных, как К. Дж. Дейт (С. J. Date), например, Введение в системы баз
данных, 8-е издание (Издательский дом “Вильямс”, 2005). Однако имейте в виду, что
представленный в таких источниках материал может оказаться в основном теорети-
ческим, поэтому иногда его трудно задействовать непосредственно в коммерческих
Web-разработках. Как правило, среднестатистические базы данных для Web не особо
сложны.
Что дальше
В следующей главе мы приступим к установке базы данных MySQL. Сначала вы
научитесь устанавливать базу данных MySQL для работы в Web и отправлять ей за-
просы, а затем отправлять запросы из РНР-кода.
Глава 8. Проектирование баз данных для Web
237
9
Создание базы
данных для Web
В этой главе мы обсудим методику установки базы данных MySQL для ее исполь-
зования на Web-сайте.
В главе, помимо прочих, рассматриваются следующие темы:
Создание базы данных.
Настройка пользователей и полномочий.
Знакомство с системами полномочий MySQL.
Создание таблиц базы данных.
Создание индексов.
Типы столбцов в MySQL.
В этой главе мы продолжим использовать в качестве примера интерактивный ма-
газин “Буквофил”, который рассматривался предыдущей главе. Давайте вспомним
схему базы данных приложения “Буквофил”:
Customers(CustomerlD, Name, Address, City)
Orders(OrderlD, CustomerlD, Amount, Date)
Books(ISBN, Author, Title, Price)
Order_Iterns(OrderlD, ISBN, Quantity)
Book_Reviews (ISBN, Reviews)
Напомним, что первичные ключи подчеркнуты, а внешние ключи представлены
курсивом.
Чтобы использовать материал этого раздела, необходимо иметь доступ к MySQL.
В большинстве случаев подразумевается, что:
На Web-сервере выполнена базовая установка MySQL, которая включает в себя:
• Инсталляцию необходимых файлов.
• Настройке пользователя MySQL.
• Настройку пути.
• Запуск mysql_instali_db при необходимости.
• Установку пароля для привилегированного пользователя.
• Удаление анонимного пользователя и тестирование базы данных.
• Запуск сервера MySQL в первый раз и его настройка на автоматический
запуск в будущем.
Если все это проделано, можно смело приступать к изучению данной главы.
Если это не так, то необходимые инструкции можно найти в приложении А.
Если на каком-то этапе работы с этой главой возникают проблемы, то они мо-
гут быть следствием неправильной настройки системы MvSQL. Если это дейст-
вительно так, обратитесь к вышеприведенном}' списку и приложению А, дабы
устранить все возможные неточности.
Имеется доступ к MySQL на компьютере, где у вас нет прав администратора
(например, на машине, поддерживающей службу Web-хостинга, на рабочей
станции и так далее).
В этом случае, чтобы работать с примерами или создать собственную базу дан-
ных, администратор должен настроить для вас учетную запись пользователя и
базу данных, после чего сообщить имя пользователя, пароль и назначенное
имя базы.
Разделы главы, в которых объясняется, как устанавливать пользователей и ба-
зы данных, можно либо пропустить, либо прочесть, чтобы легче было объяс
нить свои требования администратору. Обычному' пользователю не разрешает-
ся выполнять команды создания пользователей и баз данных.
Примеры, приведенные в этой главе, были собраны и протестированы в MySQL
5.0, доступной на момент написания книги. Некоторые предшествующие версии
MySQL, предоставляют гораздо меньшие функциональные возможности. Поэтому
рекомендуется установить наиболее новую, стабильно работающую версию или об-
новить до таковой существующую. Текущую версию можно загрузить из сайта MySQL,
по адресу http://mysql.com.
В этой книге взаимодействие с MySQL, осуществляется с использованием клиента
командной строки под названием монитора MySQL., который входит в состав каждой
версии MySQL.. Тем не менее, допускается применение и других клиентов. Например,
если вы используете MySQL, в Web-среде с услугами хостинга, администраторы пре-
доставляют браузерный интерфейс phpMyAdinin. Различные графически интерфей-
сы выглядят по-разному и могут отличаться от описанных здесь, однако предостав-
ленные инструкции легко адаптируются под любой интерфейс.
Использование монитора MySQL
Как вы увидите, примеры команд MySQL, в этой и следующей главах завершаются
точкой с запятой (;), которая сообщает MySQL, о том, что команду необходимо вы-
полнить. Если точку с запятой не поставить, ничего не произойдет. Начинающие
пользователи частенько сталкиваются с подобной проблемой.
Пропуск точки с запятой в результате приведет к тому, что команду можно будет
вводить в нескольких строках. Мы воспользовались этой возможностью, чтобы об-
легчить чтение примеров. Продолжения строк легко узнать по символу продолже-
ния, который выводит MySQL,. Он выглядит так. как показано ниже:
Глава 9. Создание базы данных для Web
239
mysql> grant select
- >
Этот символ означает, что MySQL ожидает продолжения ввода команды. До тех
пор, пока не будет введена точка с запятой, символы продолжения будут появляться
на экране после каждого нажатия клавиши <Enter>.
Следует отметить также и то, что SQL-операторы нечувствительны к регистру, а
вот базы данных и названия таблиц — чувствительны. Подробнее об этом — далее в
главе.
Вход в MySQL
Для входа в систему MySQL перейдите в командную строку и наберите следующее:
mysql -h hostname -u username -p
Команда mysql запускает монитор MySQL. Это клиент командной строки, кото-
рый соединяется с сервером MySQL.
Ключ -h используется для указания хоста, к которому нужно подключиться, то
есть к компьютеру с выполняющимся сервером MySQL. При вводе этой команды на
том же компьютере, на котором действует сервер MySQL, применять этот ключ, рав-
но как и параметр hostname, не обязательно. В противном случае параметр hostname
следует заменить именем конкретного компьютера, на котором функционирует сер-
вер MySQL.
С помощью ключа -и указывается имя пользователя (username), под которым не-
обходимо подключиться. Если имя пользователя не указано, по умолчанию будет ис-
пользоваться имя, под которым был выполнен вход в операционную систем}’.
Если сервер MySQL установлен на вашем собственном компьютере или сервере,
необходимо войти в систему под именем root (привилегированный пользователь) и
создать базу данных, о которой мы поговорим чуть позже в этом разделе. Если уста-
новка производилась впервые, то root будет единственным пользователем, который
имеет доступ к системе.
Если MySQL используется на компьютере, администратором которого является
кто-либо другой, применяйте имя пользователя, которое вам выдал администратор.
Ключ -р сообщает серверу о том, что вы хотите соединиться с использованием
пароля. Можете не указывать этот ключ, если для пользователя, под именем которого
выполняется вход в систему, пароль не требуется.
Если вы входите в систему под именем root и пароль для этого пользователя еще
не установлен, настоятельно рекомендуем прочесть приложение А и побыстрее уста-
новить пароль. Без пароля для пользователя root система, по сути, беззащитна.
Включать пароль в эту строку не обязательно — сервер MySQL запросит его само-
стоятельно. Фактически лучше пароль не включать в командную строку. Если он вве-
ден в командной строке, то появится на экране в форме обычного текста и таким об-
разом может оказаться доступным остальным пользователям.
После ввода предыдущей команды должен быть получен ответ, аналогичный сле-
дующему:
Enter password: ****
240
Часть II. Использование MySQL
(Если этого не произошло, убедитесь в том, что сервер MySQL запущен, а команда
mysql указана где-то в пути поиска.)
Теперь необходимо ввести пароль. Если все пройдет хорошо, вывод, отображае-
мый на экране, должен быть подобным следующему:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1 to server version: 5.0.O-alpha-max-debug
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql>
Вас приветствует монитор MySQL. Команды завершаются символами ; или \д.
Идентификатор вашего соединения - 1.
Номер версии сервера: 5.О.O-alpha-max-debug
Для получения справки введите 'help;' или '\h‘.
Чтобы очистить буфер, введите '\с'.
mysql>
Если на вашем компьютере такого ответа не последовало, убедитесь, что была вы-
полнена команда mysql_install_db (если это необходимо), установлен и правильно
введен пароль для пользователя root.
Если вам приходится работать на чужом компьютере, просто убедитесь в кор-
ректности ввода пароля.
Теперь на экране должно наблюдаться приглашение на ввод команды MySQL, то
есть система готова к созданию базы данных.
При работе на собственном компьютере выполните инструкции из следующего
раздела.
Если вы заходите в систему с чужого компьютера, все это уже должно быть сдела-
но. В этом случае можно сразу перейти к разделу “Использование требуемой базы
данных”. Для получения общего представления можете ознакомиться с промежуточ-
ными разделами, но описанные в них команды выполнить не удастся. (Во всяком слу-
чае, они не должны выполняться!)
Создание баз данных и пользователей
Система баз данных MySQL может поддерживать множество различных баз дан-
ных. Обычно для одного приложения будет существовать одна база данных. В нашем
примере с приложением “Буквофил” база данных будет называться books.
Создание базы данных
Создание базы данных представляет собой наиболее простую задачу. Введите в
командной строке MySQL:
mysql> create database dbname;
Вместо dbname следует указать имя базы данных, которую требуется создать. Что-
бы приступить к реализации примера приложения “Буквофил”, сейчас необходимо
создать базу данных books.
Это все, что нужно сделать. Ответ должен выглядеть подобно показанному ниже:
Глава 9. Создание базы данных для Wob
241
Query OK, 1 row affected (0.06 sec)
Запрос успешно выполнен, 1 строка изменена (0.06 с)
Это значит, что все действия были выполнены правильно. Если подобного ответа
не последовало, убедитесь, что в конце строки присутствует точка с запятой. Точка с
запятой сообщает MySQL, что ввод команды завершен и ее пора выполнять.
Настройка пользователей и полномочий
Система MySQL может поддерживать много пользователей. По соображениям
безопасности пользователь root (привилегированный пользователь) должен быть
задействован только для административных целей. Для каждого пользователя, кото-
рому необходимо работать в системе, должны быть установлены учетная запись и
пароль. Они не обязательно должны совпадать с используемыми вне MySQL (напри-
мер, именами пользователей и паролями, которые служат для входа в системы UNIX
или NT). Это же относится и к пользователю root. Вообще говоря, разумно иметь
разные пароли для входа в систему и для MySQL, особенно, если речь идет о пароле
привилегированного пользователя.
Пароли для обычных пользователей устанавливать не обязательно, но все же на-
стоятельно рекомендуется сделать это.
При создании базы данных для Web стоит создать хотя бы по одной учетной запи-
си пользователя для каждого Web-приложения.
Может возникнуть вопрос: “Зачем вообще это нужно?” Ответ обусловлен приме-
няемой системой полномочий.
Знакомство с системой полномочий MySQL
Одно из главных достоинств MySQL — поддержка сложной системы полномочий.
Полномочие (privilege) — это право определенного пользователя выполнять опре-
деленное действие над определенным объектом. Это понятие очень близко к поня-
тию прав доступа к файлам.
При создании пользователя в среде MySQL, ему предоставляются определенные
полномочия, которые определяют, что пользователь может делать в системе, а что —
нет.
Принцип минимально необходимых полномочий
Принцип минимально необходимых полномочий может использоваться для улуч-
шения защиты любой компьютерной системы. Это общий, но очень важный прин-
цип, который часто упускается из виду. Он заключается в следующем:
Пользователь (или процесс) должен обладать наименьшим уровнем полномочий, необхо-
димым для выполнения назначенной задачи.
Это справедливо для MySQL, как и для любой другой системы. Так, чтобы выпол-
нить запрос из среды Web, обычному пользователю не нужны все те полномочия, ко-
торые предоставлены привилегированному пользователю. Следовательно, потребуется
создать еще одного пользователя, который будет обладать лишь теми полномочиями,
которые необходимы для доступа к только что созданной базе данных.
242
Часть II. Использование MySQL
Настройка пользователей: команда GRANT
Команды GRANT и REVOKE применяются для предоставления и лишения прав поль-
зователей MySQL на четырех уровнях полномочий:
Глобальный.
Базы данных.
Таблицы.
Столбца.
Чуть позже будет показано, как всем этим пользоваться.
Команда GRANT служит для создания пользователей и предоставления им полно-
мочий. В общем виде команда GRANT выглядит следующим образом:
GRANT privileges [columns]
ON itern
TO user_name [IDENTIFIED BY 'password' j
[REQUIRE ssl^options]
[WITH [GRANT OPTION | limit_options] ]
Конструкции, заключенные в квадратные скобки, являются необязательными.
В данной синтаксической структуре присутствует несколько заполнителей.
Первый, privileges (полномочия), должен заполняться разделенным запятыми
списком полномочий, определенных в MySQL. Полномочия будут рассмотрены в сле-
дующем разделе.
Заполнитель columns (столбцы) необязателен. Его можно использовать для указа-
ния полномочий применительно к конкретным столбцам. При этом можно указывать
имя одного столбца либо разделенный запятыми список имен столбцов.
Заполнитель item (элемент) может быть базой данных или таблицей, к которой
применяются новые полномочия.
Указав * . * в качестве item, можно предоставить полномочия для всех баз данных
в целом. Такое действие называется предоставлением глобальных полномочий. Этот
же эффект достигается и указанием лишь одного символа *, если полномочия не
должны применяться к какой-то конкретной базе данных.
Чаще всего будут задаваться все таблицы в определенной базе данных — dbname. ★
(имя_базы_ланных.*), конкретная таблица — dbname.tablename (имя_базы_ланных.
имя_таблицы) либо определенные столбцы — dbname. tablename и список необходи-
мых столбцов в заполнителе columns. Все перечисленное представляет три других
доступных уровня полномочий: соответственно базы данных, таблицы и столбца. Если
при выдаче этой команды используется какая-то конкретная база данных, параметр
tabl ename сам по себе будет интерпретироваться как “таблица в текущей базе данных”.
В качестве значения user_name должно быть указано имя пользователя, под кото-
рым пользователь должен входить в MySQL. Помните, что оно не обязательно долж-
но совпадать с регистрационным именем, под которым выполняется вход в систему.
В MySQL user_name может включать в себя и имя хоста, что весьма удобно для того,
чтобы различать пользователей, скажем, laura (которое интерпретируется как
laura@localhost) и laura@somewhere.com. Это очень удобно, поскольку часто пользо-
ватели в различных доменах имеют одни и те же имена. Кроме того, повышается степень
защищенности системы, поскольку можно указать, откуда пользователи могут подклю-
чаться к базе данных и к каким базам данных или таблицам они могут иметь доступ.
Глава 9. Создание базы данных для Web
243
В качестве заполнителя password следует указать пароль, необходимый для входа.
Руководствуйтесь общими правилами использования паролей. О безопасности мы
поговорим чуть позже, отметим лишь, что пароль не должен быть легко угадывае-
мым. Не стоит употреблять слово из словаря или совпадающее с именем пользовате-
ля. В идеале пароль должен включать в себя прописные и строчные буквы и небук-
венные символы.
Конструкция REQUIRE позволяет указывать, что пользователь должен подклю-
чаться через SSL. (Secure Sockets Layer — уровень защищенных сокетов) и передавать
дополнительные опции для SSL. Дополнительную информацию о SSL-соединениях с
MySQL можно найти в руководстве по MySQL.
Опция WITH GRANT OPTION, если она указана, дает право пользователю предостав-
лять свои полномочия другим.
Взамен конструкции WITH можно указывать следующие конструкции:
MAX_QUERIES_PER_HOUR п
или
MAX_UPDATES_PER_HOUR п
ИЛИ
MAX_CONNECTIONS_PER_HOUR п
С помощью этих конструкций можно ограничить количество запросов, обновле-
ний или подключений, которые пользователь может инициировать в час. Они ис-
ключительно полезны для ограничения загрузки индивидуальными пользователями
совместно используемой системы.
Полномочия хранятся в пяти системных таблицах, расположенных в базе дан-
ных mysql. Эти пять таблиц называются mysql. user, mysql. db, mysql. host,
mysql. tables_priv и mysql. columns_priv. Вместо использования команды GRANT
можно непосредственно изменять эти таблицы. Подробнее эти вопросы рассматри-
ваются в главе 12.
Типы и уровни полномочий
В MySQL существуют три основных типа полномочий: полномочия, которые
можно предоставлять обычным пользователям; полномочия, которые нужны только
администраторам, и ряд специальных полномочий. Любой пользователь может полу-
чить любые полномочия, тем не менее, обычно, в соответствии с принципом мини-
мально необходимых полномочий, полномочия административного типа предостав-
ляются только администраторам.
Пользователям следует предоставлять полномочия только для тех баз данных и
таблиц, с которыми им придется работать. Доступ к базе данных mysql должен быть
закрыт для всех, кроме администратора, ибо именно в ней хранятся учетные записи
пользователей, пароли и т.п. (Эта база рассматривается в главе 12.)
Полномочия для обычных пользователей непосредственно связаны с определен-
ными командами SQL и правами их выполнения. Подробнее команды SQL рассмат-
риваются в следующей главе. А пока мы постараемся дать общее представление о вы-
полняемых ими действиях. Описание полномочий приведено в табл. 9.1. В столбце
244
Часть II. Использование MySQL
“Применяется к” перечислены объекты, к которым могут применяться полномочия
данного типа.
Таблица 9.1. Полномочия для пользователей
Полномочия Применяется к Описание
SELECT таблицам, столбцам Разрешает пользователям выбирать строки (записи) в таблицах.
INSERT . таблицам, столбцам Разрешает пользователям вставлять новые строки в таблицы.
UPDATE таблицам, столбцам Разрешает пользователям изменять значения в сущест- вующих строках таблиц.
DELETE таблицам Разрешает пользователям удалять существующие строки в таблицах.
INDEX таблицам Разрешает пользователям создавать и удалять индексы оп- ределенных таблиц.
ALTER таблицам Разрешает пользователям изменять структуру существующих таблиц, добавляя столбцы, переименовывая столбцы или таблицы и изменяя типы данных, хранящихся в столбцах.
CREATE базам данных, таблицам Разрешает пользователям создавать новые базы данных или таблицы. Если в команде GRANT указана определенная база данных или таблица, пользователь может только создавать ту или иную базу данных или таблицу, что подразумевает перво- начальное ее удаление.
DROP базам данных, таблицам Разрешает пользователям удалять базы данных или таблицы.
С точки зрения безопасности системы большая часть полномочий обычных пользо-
вателей сравнительно безобидна. Полномочия ALTER могут использоваться для обхода
системы полномочий путем переименования таблиц, однако эти полномочия требуют-
ся пользователям очень часто. При выборе средств защиты системы всегда приходится
находить компромисс между практичностью и безопасностью. В каждом конкретном
случае требуется индивидуальный подход к предоставлению полномочий ALTER, но не
следует забывать, что они предоставляются пользователям достаточно часто.
Помимо полномочий, перечисленных в табл. 9.1, существуют еще полномочия
REFERENCES и EXECUTE, которые в настоящий момент не применяются, и GRANT, кото-
рые предоставляются опцией WITH GRANT OPTION, а не в списке privileges.
В табл. 9.2 описаны полномочия, необходимые администраторам.
Эти полномочия можно предоставлять не только администраторам, но прежде
чем так поступать, следует хорошенько подумать.
Полномочия FILE несколько отличаются от других. Они очень удобны для поль-
зователей, поскольку возможность загрузки данных из файла вместо того, чтобы на-
бирать их заново, позволяет экономить массу времени. С другой стороны, у пользова-
теля есть возможность загрузить любой файл, который сервер MySQL в состоянии
увидеть, в том числе базы данных других пользователей и файлы с паролями. Пре-
доставляйте эти полномочия с осторожностью либо предлагайте пользователю за-
гружать данные.
Глава 9. Создание базы данных для Web
245
Таблица 9.2. Полномочия для администраторов
Полномочия Описание
CREATE Позволяет администратору использовать ключевое слово TEMPORARY
TEMPORARY TABLES в операторах CREATE TABLE.
FILE Позволяет помещать в таблицы данные из файлов и наоборот.
LOCK TABLES Разрешает явное использование оператора LOCK TABLES.
PROCESS Позволяет администратору просматривать серверные процессы, отно- сящиеся ко всем пользователям.
RELOAD Позволяет администратору перезагружать таблицы предоставления пол- номочий и сбрасывать полномочия, хосты, файлы журналов и таблицы.
REPLICATION Разрешает использовать SHOW STATUS на ведущих и ведомых серверах
CLIENT репликации. Репликация описана в главе 12.
REPLICATION Разрешает ведомым серверам репликации подключаться к ведущему'
SLAVE серверу. Репликация описана в главе 12.
SHOW DATABASES Позволяет с помощью оператора SHOW DATABASES получать список всех баз данных. Без этой привилегии выводятся только базы данных, для которых имеются другие привилегии.
SHUTDOWN Позволяет администратору останавливать сервер MySQL.
SUPER Позволяет администратору удалять потоки, относящиеся к любом}7 пользователю.
Существуют также два специальных полномочия, которые описаны в табл. 9.3.
Таблица 9.3. Специальные полномочия
Полномочия Описание
ALL Предоставляет все полномочия, перечисленные в таблицах 9.1 и 9.2. Вместо ALL можно также написать ALL PRIVILEGES.
USAGE Не предоставляет никаких полномочий. Подобным образом можно создать пользователя, дать ему возможность входить в систем}’, но не разрешать ему что-либо делать. Как правило, со временем такой поль- зователь получает дополнительные полномочия.
Команда REVOKE
Противоположной команде GRANT является команда REVOKE. Она используется для
лишения пользователя полномочий и по синтаксису сходна с командой GRANT:
REVOKE privileges [(columns)]
ON item
FROM user_name
Если полномочия были предоставлены с конструкцией WITH GRANT OPTION, их
можно удалить (вместе со всеми другими полномочиями) следующим образом:
REVOKE ALL PRIVILEGES, GRANT
FROM user_name
246
Часть II. Использование MySQL
Примеры использования команд GRANT и REVOKE
Для предоставления полномочий администратору можно набрать:
mysql> grant all
-> on *
- > to fred identified by 'mnbl23'
- > with grant option;
Эта команда предоставляет пользователю с именем fred и паролем птЫ23 все
полномочия для всех баз данных с правом их передачи другим пользователям.
Если этот пользователь в системе не нужен, лишите его всех полномочий:
mysql> revoke all
-> on *
-> from fred;
Теперь можно определить обычного пользователя без каких-либо полномочий:
mysql> grant usage
- > on books.*
- > to sally identified by 'magicl23';
Поговорив с Салли (sally), можно больше узнать о ее намерениях и в результате
предоставить ей необходимые полномочия:
mysql> grant select, insert, update, delete, index, alter, create, drop
- > on books.*
-> to sally;
Обратите внимание, что для предоставления привилегий не требуется указывать
пароль Салли.
Если мы пришли к выводу; что Салли натворила что-то нехорошее в базе данных,
ее полномочия можно ограничить:
mysql> revoke alter, create, drop
- > on books.*
- > from sally;
Позже, когда ей не нужно будет пользоваться базой данных, ее можно лишить во-
обще всех полномочий:
mysql> revoke all
-> on books.’
-> from sally;
Установка пользователя для доступа из Web
Чтобы PHP-сценарии могли подключаться к MySQL, потребуется настроить соот-
ветствующего пользователя. В этом случае также можно применить принцип мини-
мально необходимых полномочий. При этом следует задаться вопросом: “Какие дей-
ствия должны иметь право выполнять сценарии?”
В большинстве случаев сценариям понадобится проводить над строками таблиц
только операции SELECT, INSERT, DELETE и UPDATE. Можно поступить следующим об-
разом:
Глава 9. Создание базы данных для Web
247
mysql> grant select, insert, delete, update
- > on books.*
- > to bookorama identified by 'bookoramal23';
Понятно, что для большей безопасности следует выбрать более надежный пароль.
Если вы используете службу Web-хостинга, можно предположить, что вам будут
предоставлены другие полномочия для созданной для вас базы данных. Как правило,
вам будут присвоены одни и те же имя_пользователя и пароль для работы из команд-
ной строки (настройка таблиц и так далее) и для подключения к MySQL из Web-
сценариев (выдача запросов к базе данных). Это катастрофически снижает безопас-
ность. Пользователя с таким уровнем полномочий можно установить следующим об-
разом:
mysql> grant select, insert, update, delete, index, alter, create, drop
- > on books.*
- > to bookorama identified by 'bookoramal23';
Теперь можно приступить к настройке второго пользователя.
Выход из системы привилегированного пользователя
Покинуть монитор MySQL можно, набрав quit. После этого имеет смысл войти в
систему в качестве пользователя Web и убедиться, что все работает должным обра-
зом. Если выданный ранее оператор GRANT выполнился, но доступ оказывается не-
возможным, это значит, что вы не удалили анонимных пользователей во время ин-
сталляции MySQL. Войдите в систем)’ вновь как root и удалите учетные записи
анонимных пользователей в соответствии с инструкциями, представленными в при-
ложении А. После этого вход в систему пользователя Web станет возможным.
Использование требуемой базы данных
Если вы дошли до этой стадии, то должны находиться в системе под учетной запи-
сью MySQL уровня пользователя и быть готовыми к тестированию примера кода не-
зависимо от того, кто его установил — вы или администратор Web-сервера.
После входа в систему сначала потребуется указать базу данных, с которой необ-
ходимо работать. Это можно сделать следующим образом:
raysql> use dbname;
где dbname — имя соответствующей базы данных.
Можно и не вводить команду use, но тогда база данных должны быть указана во
время входа в систему:
mysql -D dbname -h hostname -u username -p
В этом примере мы будем работать с базой данных books:
mysql> use books;
После ввода этой команды MySQL должен вывести следующую строку:
Database changed
База данных изменена
248
Часть II. Использование MySQL
Если перед началом работы база данных не была выбрана, MySQL выведет сооб-
щение об ошибке:
ERROR 1046: No Database Selected
ОШИБКА 1046: He выбрана база данных
Создание таблиц баз данных
Следующий этап настройки базы данных связан с созданием таблиц. Это делается
с помощью SQL-команды CREATE TABLE. Общая форма оператора CREATE TABLE вы-
глядит следующим образом:
CREATE TABLE tablename(columns)
Заполнитель tablename необходимо заменить именем конкретной таблицы, кото-
рую требуется создать, a columns — разделяемым запятыми списком столбцов в таб-
лице.
Каждый столбец должен иметь имя, за которым следует тип данных.
Снова вспомним схему базы данных “Буквофил”:
Customers(CustomerlD, Name, Address, City)
Orders(OrderlD, CustomerlD, Amount, Date)
Books(ISBN, Author, Title, Price)
Order_Iterns(OrderlD, ISBN, Quantity)
Book_Rev i ews(ISBN, Revi ews)
В листинге 9.1 показан SQL-код для создания этих таблиц, при этом подразу-
мевается, что база данных books уже существует. Этот код можно найти в файле
chapter09/bookorama.sql на прилагаемом компакт-диске.
Существующий SQL-файл, подобный загруженному с компакт-диска, в MySQL
можно запустить следующим образом:
> mysql -h host -u bookorama -D books -p < bookorama.sql
(He забудьте заменить заполнитель host именем используемого хоста.)
В данном случае удобно использовать перенаправление, поскольку перед выпол-
нением SQL-код можно отредактировать в любом текстовом редакторе.
Листинг 9.1. bookorama. sql — SQL-код создания таблиц для приложения “Буквофил”
create table customers
( customerid int unsigned not null auto_increment primary key,
name char(50) not null,
address char(lOO) not null,
city char(30) not null
) ;
create table orders
( orderid int unsigned not null auto_increment primary key,
customerid int unsigned not null,
amount float(6,2),
date date not null
) ;
Глава 9. Создание базы данных для Web
249
create table books
( isbn char(13) not null primary key,
author char(50),
title char(100),
price float(4,2)
) ;
create table order_items
( orderid int unsigned not null,
isbn char(13) not null,
quantity tinyint unsigned,
primary key (orderid, isbn)
) ;
create table book_reviews
(
isbn char(13) not null primary key,
review text
);
Каждая таблица создается с помощью отдельного оператора CREATE TABLE. Как ви-
дите, создаются все таблицы из схемы, со столбцами, спроектированными в преды-
дущей главе. Определение каждого столбца содержит его имя, за которым следует
тип данных. В определениях некоторых столбцов пристутствуют и другие специфи-
каторы.
Значения других ключевых слов
NOT NULL означает, что все строки таблицы должны иметь значение в этом атри-
буте. Если ключевое слово NOT NULL не указано, поле может быть пустым (NULL).
AUTO_INCREMENT — это специальная функция MySQL, которую можно использовать
применительно к числовым столбцам. Если при вставке строк в таблицу оставить это
поле пустым, MySQL автоматически сгенерирует значение уникального идентифика-
тора. Это значение будет на единицу больше максимального значения, еже сущест-
вующего в столбце. Каждая таблица может содержать не более одного такого поля.
Столбцы, для которых указано ключевое слово AUTO_INCREMENT, должны быть проин-
дексированы.
Ключевое слово PRIMARY KEY, следующее за именем столбца, определяет, что этот
столбец является первичным ключом таблицы. Записи в этом столбце должны быть
уникальными. MySQL будет автоматически индексировать этот столбец. Обратите
внимание, что ранее, когда столбец customerid использовался в таблице customers,
это делалось с применением AUTO_INCREMENT. Автоматическое индексирование по
первичному ключу обеспечивает индексирование, требуемое для применения функ-
ции AUTO_INCREMENT.
Указывать PRIMARY KEY после названия столбца можно лишь тогда, когда мы име-
ем дело с первичным ключом в виде одиночного столбца. Альтернативный вариант —
конструкция PRIMARY KEY в конце оператора создания таблицы order_items. В дан-
ном случае эта форма была использована потому, что первичный ключ состоит из
двух столбцов. (Это также создает индекс, основанный на двух столбцах.)
250
Часть II. Использование MySQL
Ключевое слово UNSIGNED, заданное после целочисленного типа, означает, что
соответствующее значение может быть только нулевым или положительным (то есть
беззнаковым).
Что означают типы столбцов
В качестве примера рассмотрим первую таблицу:
create table customers
( customerid int unsigned not null auto_increment primary key,
name char(50) not null,
address char(100) not null,
city char(30) not null
) ;
При создании любой таблицы необходимо принимать решения в отношении ти-
пов столбцов.
В соответствии со схемой таблица customers содержит четыре столбца. Первый,
customerid, — это первичный ключ, который определен непосредственно. Согласно
нашем}- решению, он будет представляться целым числом (тип данных int), причем
эти идентификаторы должны быть беззнаковыми (unsigned). Кроме того, мы восполь-
зовались auto_increment, поэтому MySQL позаботится о присвоении уникальных
идентификаторов — а нам одной заботой меньше.
Все остальные столбцы будут содержать данные строкового типа. Для них выбран
тип char. Он определяет поля фиксированной ширины. Ширина указывается в скоб-
ках, поэтому, например, имя (поле name) может содержать до 50 символов.
Этот тип данных всегда будет выделять для хранения имен память длиной 50 сим-
волов, даже если в действительности имена будут короче. MySQL будет дополнять
данные соответствующим количеством пробелов. Альтернативным типом данных
является varchar, который использует только необходимый объем памяти (плюс
один байт). Таким образом, приходится идти на небольшой компромисс — varchar
использует меньше памяти, зато char работает быстрее.
Обратите внимание, что все столбцы объявлены как NOT NULL. Это минимальная
оптимизация, которую можно выполнять практически во всех случаях. Более под-
робно вопросы оптимизации рассматриваются в главе 12.
Некоторые операторы CREATE отличаются по синтаксису. Взгляните на таблицу
orders:
create table orders
( orderid int unsigned not null auto_increment primary key,
customerid int unsigned not null,
amount float(6,2),
date date not null
) ;
Значения столбца amount определены как числа с плавающей точкой (тип float).
Для большинства типов данных с плавающей точкой можно определить ширину ото-
бражения данных и количество десятичных разрядов. В данном случае сумма заказа
будет выражаться в долларах, поэтому выбрана сравнительно большая ширина ото-
бражения итоговой суммы (6) и два десятичных разряда для представления центов.
Глава 9. Создание базы данных для Web
251
Столбец date имеет тип данных date.
В данной таблице указано, что все столбцы, кроме столбца amount, должны быть
NOT NULL. Почему? Когда в базу данных вносится заказ, его необходимо сохранить в
таблице orders, добавить элементы в таблицу order_items и только затем подсчитать
сумму заказа. На этапе создания заказа сумма заказа не известна, поэтому она может
иметь значение NULL.
Таблица books обладает похожими характеристиками:
create table books
( isbn char(13) not null primary key,
author char(50),
title char(100),
price float(4,2)
) ;
В этом случае не требуется генерировать первичный ключ, потому что номера
ISBN генерируются в другом месте. Остальные поля оставлены NULL, поскольку в пер-
вую очередь книжный магазин получает ISBN, а потом уже название книги, авторов и
цен}-.
Таблица order_items служит примером применения первичных ключей с множе-
ством столбцов:
create table order_items
( orderid int unsigned not null,
isbn char(13) not null,
quantity tinyint unsigned,
primary key (orderid, isbn)
) ;
Тип данных количества экземпляров конкретной книги определен как TINYINT
UNSIGNED; этот тип данных может принимать целочисленные значения от 0 до 255.
Как упоминалось ранее, первичные ключи с множеством столбцов должны опре-
деляться с помощью специальной конструкции первичного ключа, что как раз ис-
пользуется в данном случае.
И, наконец, рассмотрим таблицу book_reviews:
create table book_reviews
(
isbn char(13) not null primary key,
review text
) ;
В этой таблице используется новый тип данных, о котором мы еще не говорили.
Он предназначен для объемных текстов, например, статей. Существует несколько
вариантов данного типа, и они рассматриваются далее в этой главе.
Чтобы лучше разобраться в создании таблиц, стоит начать с имен столбцов и
идентификаторов в целом, а потом уже перейти к типам данных. Для начала взглянем
на созданную нами базу данных.
252
Часть II. Использование MySQL
Просмотр базы данных с помощью команд
SHOW и DESCRIBE
Войдите в монитор MySQL и начните работу с базой данных books. Таблицы в базе
можно просмотреть следующим образом:
mysql> show tables;
MySQL отобразит список таблиц базы данных:
| Tables in books |
| book_reviews |
| books I
| customers |
| order_iterns |
| orders |
5 rows in set (0.06 sec)
(5 строк в наборе (0.06 с))
Команду show можно применять и для просмотра списка баз данных:
mysql> show databases;
Если у вас нет привилегий SHOW DATABASES, вы будете видеть только базы данных,
для которых у вас установлены привилегии.
Команда DESCRIBE дает возможность увидеть дополнительную информацию по
конкретной таблице, например, books:
mysql> describe books;
MySQL выведет информацию, которая была введена во время создания базы
данных:
Field -+ | Type | Null 1 Key I Default | Extra
isbn | char(13) 1 PRI !
author | char(50) | YES 1 | NULL |
title | char(100) | YES | NULL
price | float(4,2) - + 1 YES | NULL
4 rows in set (0.00 sec)
Поле | Тип Null | Ключ | По умолчанию | Дополнительно
isbn | char(13) 1 PRi 1 1
author 1 char(50) i YES | | NULL |
ti tie | char(100) YES | j NULL
price j float(4,2) YES | i NULL
4 строки в наборе (0.00 с)
Глава 9. Создание базы данных для Web
253
Эти команды полезны, если требуется вспомнить, какие типы столбцов использу-
ются, либо если приходится работать с базой данных, которая была создана кем-то
другим.
Создание индексов
Мы уже кратко упоминали об индексах, поскольку установка первичных ключей
связана с созданием индексов по соответствующим столбцам.
Одна из общих проблем, с которыми сталкиваются новички в MySQL, состоит в
том, что они сталкиваются с достаточно низкой производительностью, в то время как
источники утверждают, наоборот, об исключительно высокой производительности
MySQL. Проблема, связанная с низкой производительностью, возникает из-за того,
что новички попросту забывают о создании индексов в своих базах данных. (В MvSQL
допускается создавать таблицы без первичных ключей либо индексов.)
Для начала рассмотрим, что будут делать индексы, которые создаются автомати-
чески. Если оказывается, что вы запускаете множество запросов в столбце, который
не является ключевым, можете создать по нему индекс и тем самым увеличить произ-
водительность. Индекс создается с помощью оператора CREATE INDEX. Общий син-
таксис этого операторы выглядит следующим образом:
CREATE [UNIQUE I FULLTEXT] INDEX index_name
ON table_name (index_column_name [(length}] [ASC|DESCj, . ,.]i
Здесь index_name — имя индекса, table_naiae — имя таблицы, a index_column_narae —
имя столбца, по которому создается индекс. (Индексы типа FULLTEXT используются
для текстовых полей; мы рассмотрим их подробно в главе 13.)
Необязательное поле length (длина) позволяет указать, что индексироваться
должны только первые length символов столбца. Можно также выбрать, как должна
выполняться индексация: по возрастанию (ASC) или по убыванию (DESC); по умолча-
нию принимается ASC.
Замечание по поводу типов таблиц
Возможно, вам известно, что MySQL поддерживает более одного типа таблиц или
механизмов хранения, включая несколько типов, безопасных в отношении транзак-
ций. Типы таблиц будут обсуждаться в главе 13. До настоящего момента все таблицы в
базе данных используют механизм хранения по умолчанию — MylSAM.
Идентификаторы MySQL
В MySQL используются пять видов идентификаторов — базы данных, таблицы,
столбцы, индексы (с ними вы уже знакомы) и псевдонимы (о них мы поговорим в сле-
дующей главе).
Базы данных в MySQL отображаются на каталоги лежащей в их основе файловой
структуры, а таблицы — на файлы. Это отображение напрямую влияет на присваи-
ваемые им имена, а также на зависимость этих имен от регистра — если в установлен-
ной операционной системе (ОС) имена файлов и каталогов зависят от регистра, то и
имена баз данных и таблиц также будут от него зависеть (как, например, в UNIX), в
противном случае — нет (например, в Windows). Имена столбцов и псевдонимы не
254
Часть II. Использование MySQL
зависят от регистра, однако в одном и том же SQL-операторе нельзя применять и
строчные, и прописные символы.
К слову, расположение каталогов и файлов, содержащих данные, будет таким, ка-
ким оно установлено в конфигурации. Проверить их расположение можно с помо-
щью утилиты mysqladmin:
mysqladmin variables
В полученном выводе найдите переменную datadir.
Краткий список возможных идентификаторов приведен в табл. 9.4. Единственное
дополнительное ограничение состоит в невозможности использования в идентифи-
каторах символов ASCII(O), ASCII(255) или символа кавычки (откровенно говоря,
довольно-таки трудно предположить, для чего они могли бы пригодиться).
Таблица 9.4. Идентификаторы MySQL
Тип Макс, длина Чувствительность к регистру Допустимые символы
База данных 64 так же, как в ОС Все символы, допустимые в именах каталогов ОС, за исключением символа / , \ и . (точка).
Таблица 64 так же, как в ОС Все символы, допустимые в именах файлов ОС. за исключением символов / и . (точка).
Столбец 64 нет Все.
Индекс 64 нет Все.
Псевдоним 255 нет Все.
Нетрудно заметить, что правила являются предельно свободными.
Начиная с версии MySQL 3.23.6, в идентификаторах можно даже использовать за-
резервированные слова и специальные символы всех видов. Единственное ограниче-
ние — если вы все же применяете всякого рода странности, то их необходимо заклю-
чать в обратные кавычки (на большинстве клавиатур они находятся на клавише с
тильдой (~) в верхнем левом углу). Например:
create database 'create database';
В предыдущих версиях MySQL (до 3.23.6) правила более строги и не допускают
подобных вольностей.
Однако какой бы ни была свобода действий, не стоит забывать о здравом смысле.
То, что базу данных можно назвать ' create database', вовсе не означает, что так и
следует поступать. Здесь, как и в любой другой области программирования, должен
применяться один принцип — идентификаторы должны быть как можно более ос-
мысленными.
Типы данных столбцов
В MySQL определены три базовых типа столбцов: числовой, дата и время, а также
строковый. Каждая из этих категорий подразделяется на множество типов. В этой
главе мы кратко их рассмотрим, а со всеми их преимуществами и недостатками под-
робно ознакомимся в главе 12.
Глава 9. Создание базы данных для Web
255
Каждый из упомянутых выше трех типов требует использования различного объ-
ема памяти. При выборе типа столбца, главное — выбрать тип, требующий наимень-
шего объема памяти, в котором все же помещаются данные.
Для многих типов данных при создании столбца выбранного типа можно задавать
максимальную ширину отображения. В приведенных ниже таблицах типов данных
этот параметр обозначается как М. Если для данного типа он не обязателен, его при-
водят в квадратных скобках. Максимальное значение М составляет 255.
Необязательные значения во всех описаниях заключены в квадратные скобки.
Числовые типы
Числовые типы представляют либо целые числа, либо числа с плавающей точкой.
Для чисел с плавающей точкой можно указывать количество цифр после десятичной
точки. В нашей книге этот параметр обозначен как D. Максимальное значение, кото-
рое можно выбрать для D, составляет 30, или М — 2 (то есть максимальная ширина
отображения минус два — один символ для вывода десятичной точки и второй для
целой части числа), в зависимости от того, какое значение окажется меньшим.
Целочисленный тип может также быть определен как UNSIGNED, как показано в
листинге 9.1.
Всем числовым типам можно присвоить атрибут ZEROFILL. Такие значения будут
отображаться на экране с ведущими нулями.
Целочисленные типы перечислены в табл. 9.5. Обратите внимание, что диапазо-
ны для чисел со знаком приводятся в одной строке, а чисел без знака — во второй.
Таблица 9.5. Целочисленные типы данных
Тип Диапазон Память (байт) Описание
TINYINTf (М) ] -127..128 или 0..255 1 Очень маленькие целые числа
BIT 1 Синоним TINYINT
BOOL 1 Синоним TINYINT
SMALLINT[ (М) ] -32768..32767 или 0..65535 2 Маленькие целые числа
MEDIUMINT[ (М) ] -8388608..8388607 или 0..16777215 3 Целые числа средней величины
INT[ (М) ] 31 .31 -2 ..2 -1 4 Обычные целые числа
32 или 0..2 - 1
INTEGER [ (М) ] Синоним INT
BIGINT [ (М) ] 63 63 _ -2 ..2 -1 64 или 0..2 - 1 8 Большие целые числа
Типы данных с плавающей запятой перечислены в табл. 9.6.
256
Часть II. Использование MySQL
Таблица 9.6. Типы данных с плавающей запятой
Тип Диапазон Память (байт) Описание
FLOAT(точность) зависит от точности различна Может использоваться для определения чисел с плаваю- щей точкой одинарной или двойной точности.
FLOAT!(М, D)] ±1.175494351Е-38 ±3.402823466Е+38 4 Числа с плавающей точкой одинарной точности. Эквива- лентно FLOAT(4), но только с указанной шириной отобра- жения и количеством деся- тичных разрядов.
DOUBLE!(М, D)] ±1.7976931348623157Е+308 ±2.2250738585072014Е-308 8 Числа с плавающей точкой двойной точности. Эквива- лентно FLOAT(8), но только с указанной шириной отобра- жения и количеством деся- тичных разрядов.
DOUBLE PRECISION!(M,D)] ±1.7976931348623157Е+308 ±2.2250738585072014Е-308 8 Синоним DOUBLE! (M,D) ].
REAL[(M, D)J ±1.7976931348623157Е+308 ±2.2250738585072014Е-308 8 Синоним DOUBLE [ (М, D) ].
DECIMAL! (M[,D]) ] различный М+2 Число с плавающей точкой, сохраненное как char. Диапа- зон зависит от ширины ото- бражения М.
NUMERIC[(M, D)] различный М+2 Синоним DECIMAL.
DEC[(M, D)] различный М+2 Синоним DECIMAL.
FIXED [ (M, D) ] различный М+2 Синоним DECIMAL.
Типы даты и времени
Система MySQL поддерживает несколько типов даты и времени, которые приве-
дены в табл. 9.7. Эти типы позволяют вводить данные либо в строковом, либо в чи-
словом формате. Нет ничего сложного в установке значения в определенной строке
столбца типа TIMESTAMP равным дате и времени последней операции, выполненной
над этой строкой, даже если не делать это вручную. Это свойство весьма полезно при
записи информации о транзакциях.
В табл. 9.8 перечислены возможные типы отображения для формата TIMESTAMP.
Глава 9. Создание базы данных для Web
257
Таблица 9.7. Типы данных даты и времени
Тип Диапазон Описание
DATE 1000-01-01 9999-12-31 Дата. Отображается в виде ГГГГ-ММ-ЦЛ.
TIME -838:59:59 838:59:59 Время. Отображается в виде ЧЧ:ММ: СС. Легко заметить, что диапазон намного шире, чем мо- жет когда-либо пригодиться.
DATETIME 1000-01-01 00:00:00 9999-12-31 23:59:59 Дата и время. Отображается в виде ГГГГ-ММ-ДДЧЧ:ММ:СС.
TIMESTAMP [ (M) ] 1970-01-01 00:00:00 Какой-то момент в 2037 год} Метка времени, полезная для формирования отчетов по транзакциям. Формат отображения зависит от значения М (см. ниже табл. 9.8). Верхнее значение диапазона зависит от ограни- чений UNIX.
YEAR[(2 j 4)] 70-69 (1970-2069) 1901-2155 Год. Может быть указан в двух- или четырехсим- вольном формате. Для каждого из них. как пока- зано, определен свой диапазон.
Таблица 9.8. Типы отображения для формата TIMESTAMP
Тип Формат отображения
TIMESTAMP птгммддччммсс
TIMESTAMP(14) ггггммддччммсс
TIMESTAMP(12) ггммддччммсс
TIMESTAMP(10) ггммддччмм
TIMESTAMP(8) птгммдд
TIMESTAMP(6) ггммдд
TIMESTAMP(4) ггмм
TIMESTAMP(2) гг
Строковые типы
Строковые типы подразделяются на три группы. Первая группа — простые “ста-
рые добрые” строки, которые представляют собой короткие фрагменты текста. Это
типы CHAR (символы фиксированной длины) и VARCHAR (символы переменной дли-
ны). Ширину каждого из них можно указать. Столбцы с типом CHAR будут дополняться
пробелами до максимальной ширины, независимо от размеров данных, в то время
как в столбцах с типом VARCHAR ширина зависит от размеров данных. (Следует отме-
тить, что MySQL усекает пробелы в конце текстовых строк типа CHAR во время извле-
чения и в конце строк типа VARCHAR во время сохранения.) При работе с этими типами
приходится искать компромисс между занимаемым объемом памяти и скоростью об-
работки. Подробнее этот вопрос будет рассмотрен в главе 12.
Вторая группа — это типы TEXT и BLOB. Их размеры могут быть разными. Первый
тип данных предназначен для более длинных текстовых фрагментов, второй — для
258
Часть II. Использование MySQL
бинарных данных. BLOB означает binari large object (большой двоичный объект) и может
содержать любые данные, в том числе данные изображений или звуковых последова-
тельностей.
На практике столбцы TEXT и BLOB идентичны, за исключением того, что данные
типа TEXT зависят от регистра, a BLOB — нет. Поскольку столбцы этих типов могут
хранить большие объемы данных, их применение требует более детального анализа,
который приводится в главе 12.
К третьей группе принадлежат два специальных типа SET и ENUM. Тип SET исполь-
зуется для указания того, что значения в данном столбце принадлежат конкретному
набору фиксированных значений. Значения столбца могут содержать несколько зна-
чений из набора. Фиксированный набор может содержать до 64 элементов.
Тип ENUM представляет перечисление. Этот тип весьма схож с SET, за исключени-
ем того, что столбцы этого типа могут содержать лишь одно из фиксированных зна-
чений или значение NULL, а максимальное количество элементов в перечислении со-
ставляет 65 535.
Краткие описания строковых типов данных можно найти в таблицах 9.9, 9.10 и
9.11. Таблица 9.9 содержит описание простых строковых типов.
Таблица 9.9. Обычные строковые типы
Тип Диапазон Описание
[NATIONAL] От 1 до 255 Строки фиксированной длины М, где А1 находится между 1 и
CHAR (И) СИМВОЛОВ 255. Ключевое слово NATIONAL указывает на то, что должен
[BINARY | ASCII | UNICODE] использоваться набор символов, установленный по умолча- нию. В MySQL так и происходит по умолчанию, но на это стоит обратить внимание, поскольку данное соглашение — часть стандарта ANSI SQL. Ключевое слово BINARY указыва-
ет, что данные должны рассматриваться как зависящие от
регистра. (По умолчанию данные зависят от регистра.) Клю- чевое слово ASCII указывает, что для данного столбца будет использоваться набор символов latinl, а ключевое слово UNICODE — набор символов ucs.
CHAR 1 Синоним CHAR (1)
[NATIONAL] От 1 до 255 То же самое, за исключением того, что данные типа VARCHAR
VARCHAR (M) [BINARY] СИМВОЛОВ могут иметь произвольную длину.
Таблица 9.10 предлагает описание типов TEXT и BLOB. Максимальная длина поля
TEXT в символах равна максимальному’ размеру в байтах файла, который может хра-
ниться в этом поле.
Таблица 9.10. Типы TEXT и BLOB
Тип Максимальная длина (в символах) Описание
TINYBLOB 2- 1 (255) Маленькое поле BLOB
TINYTEXT 2 - 1 (255) Маленькое поле TEXT
BLOB 2lb - 1 (65 535) Нормальное поле BLOB
TEXT 2 - 1 (65 535) Нормальное поле TEXT
Глава 9. Создание базы данных для Web
259
Окончание табл. 9.10
Тип Максимальная длина (в символах) Описание
MEDIUMBLOB 224 - 1 (16 777 215) Среднее поле BLOB
MEDIUMTEXT 2М-1 (16 777 215) Среднее поле TEXT
LONGBLOB 232 - 1 (4 294 967 295) Большое поле BLOB
LONGTEXT 232 - 1 (4 294 967 295) Большое поле TEXT
Таблица 9.11 содержит описание типов ENUM и SET.
Таблица 9.11. Типы ENUM и SET
Тип Максимальное количество значений в наборе Описание
ENUM ('valuel' , ’valие2',...) 65 535 Столбцы этого типа могут содержать только одно из перечисленных значе- ний либо NULL.
SET {'valuel', 'value2’,...) 64 Столбцы этого типа могут содержать набор указанных значений либо NULL.
Дополнительные источники информации
Дополнительные сведения о настройке баз данных можно найти в онлайновом ру-
ководстве по MySQL, которое доступно по адресу http: / /www.mysql.com/.
Что дальше
Теперь, когда вы научились создавать пользователей, базы данных и таблицы, на-
конец-то, можно сосредоточить основное внимание на взаимодействии с базой дан-
ных. В следующей главе мы рассмотрим, как вносить данные в таблицы, обновлять и
удалять их, а также как и отправлять запросы к базе данных.
260
Часть II. Использование MySQL
10
Работа с базой
данных MySQL
В этой главе мы рассмотрим язык структурированных запросов SQL (Structured
Query Language) и его применение для выполнения запросов к базам данных.
На примере базы данных приложения “Буквофил” мы покажем, как вставлять, уда-
лять и обновлять данные, равно как и отправлять запросы в базу данных.
В главе, помимо прочих, рассматриваются следующие темы:
Что такое SQL?
Вставка данных в базу данных.
11звлечение данных из базы данных.
Соединение таблиц.
Использование подзапросов.
Обновление записей в базе данных.
Изменение таблиц после создания.
Удаление записей из базы данных.
Удаление таблиц.
Мы начнем с рассмотрения того, что собой представляет и чем полезен язык SQL.
Если база данных приложения “Буквофил” еще не установлена, это придется сде-
лать, прежде чем можно будет запустить SQL-запросы, рассматриваемые в настоящей
главе. Инструкции по установке можно найти в главе 9. (В дальнейшем, для кратко-
сти, мы будем употреблять просто БД “Буквофил”, имея в виду под этим базу данных,
лежащую в основе приложения “Буквофил”.)
Что такое SQL?
SQL — это аббревиатура от Structured Query Language (язык структурированных за-
просов). Он является стандартным языком для доступа к системам управления реляци-
онными базами данных (СУРБД, relational database management system — RDBMS). SQL
используется для сохранения данных в базе данных и последующего их извлечения из
нее. Его применяют в таких системах баз данных, как MySQL, Oracle, PostgreSQL,
Sybase, Microsoft SQL Server и множестве других.
Для SQL существует стандарт ANSI, и в основном системы, подобные MySQL, раз-
работаны с таким расчетом, чтобы обеспечить его реализацию. Существует ряд не-
значительных различий между стандартным языком SQL и SQL системы MySQL. Не-
которые из различий планируется устранить в последующих версиях MySQL, другие
же введены умышленно. По мере изложения материала мы укажем наиболее значи-
тельные различия. Полный перечень различий между ANSI SQL и SQL любой версии
MySQL можно найти в онлайновом руководстве по MySQL, которое доступно на сайте
по следующему адресу:
http://www.mysql.com/doc/en/Compatibil.ity.html
а также во множестве других источников.
Наверняка вам уже доводилось слышать о языке определения данных (Data Definition
Language — DDL), который используется для определения баз данных, и о языке мани-
пулирования данными (Data Manipulation Language — DML), применяемом для выдачи
запросов к базам данных. SQL предоставляет основные функции обеих языков. В гла-
ве 9 мы рассмотрели определение данных (функция, относящаяся к DDL) в SQL, по-
этому вы уже немного знакомы с применением DDL. Язык DDL используется при
первоначальной установке базы данных.
Функции DML используются в SQL намного чаще, поскольку именно таким обра-
зом реальные данные сохраняются в базах данных и извлекаются из них.
Вставка данных в базу данных
Прежде чем приступить к серьезной работе с базой данных, в ней необходимо со-
хранить какие-нибудь данные. Наиболее приемлемый способ предусматривает ис-
пользование SQL-оператора INSERT.
Вспомните, что СУРБД содержат таблицы, которые, в свою очередь, содержат
строки данных, организованные по столбцам. Как правило, каждая строка в таблице
описывает какой-то реальный объект или отношение, а значения столбцов в этой
строке хранят информацию о реальном объекте. Оператор INSERT можно использо-
вать для внесения строк с данными в базу данных.
Типичная форма оператора INSERT выглядит следующим образом:
INSERT [INTO] table [(columnl, column?, columns, ...)]
VALUES (valuel, value?, values, ...);
Например, чтобы вставить запись в таблицу клиентов (customers) БД "Буквофил”.
можно ввести:
insert into customers values
(NULL, "Julie Smith", "25 Oak Street", "Airport ’.-Jest");
Как видите, мы заменили table реальным именем таблицы, в которую требуется
внести данные, a valuel, value?, values и так далее — конкретными значениями.
В данном примере значения заключены в двойные кавычки. В MySQL строки всегда
должны заключаться в пару одинарных или двойных кавычек. (В этой книге встреча-
ются оба варианта.) Числа и даты в кавычках не нуждаются.
С оператором INSERT связано несколько интересных моментов.
262
Часть II. Использование MySQL
Указанные значения будут использоваться для заполнения столбцов таблицы по
порядку. Если необходимо заполнить только отдельные столбцы, или если желатель-
но указать их в ином порядке, список конкретных столбцов можно поместить в ту
часть оператора, которая относится к столбцам. Например:
insert into customers (name, city) values
("Melissa Jones", "Nar Nar Goon North");
Такой подход полезен при наличии лишь частичной информации о конкретной
записи или если несколько полей записи являются необязательными. Аналогичный
результат можно получить, прибегнув к следующему синтаксису:
insert into customers
set name="Michael Archer",
address="12 Adderley Avenue",
city="Leeton";
Вы могли обратить внимание, что столбцу customerid было присвоено нулевое
(NULL) значение при добавлении клиентки Джули Смит (Julie Smith), а при добавле-
нии других клиентов этот столбец просто игнорируется. Если помните, при создании
базы данных столбец customerid был создан как первичный ключ таблицы
customers. Поэтому такой подход может показаться странным. Однако это поле было
определено как AUTO_INCREMENT. Это означает, что при вставке в это поле строки со
значением NULL или без значения MySQL автоматически сгенерирует следующее по
порядку число последовательности и вставит его. Это свойство может исключитель-
но полезно.
В таблицу можно также вставлять несколько строк сразу. Каждая строка должна
быть заключена в отдельные скобки, разделенные запятыми.
Для представления INSERT возможны лишь несколько вариаций. После слова IN-
SERT можно указать LOW_PRIORITY или DELAYED. Ключевое слово LOW_PRIORITY озна-
чает, что система может подождать и выполнить вставку позже, когда данные не
будут читаться из таблицы. Ключевое слово DELAYED указывает, что вставляемые
данные будут буферизироваться. Если сервер занят, вы сможете продолжать выпол-
нять запросы, а не ожидать завершения операции INSERT.
Непосредственно после LOW_PRIORITY или DELAYED можно указать необязательное
ключевое слово IGNORE, которое означает, что при попытке вставки строк, которые
вызывают дублирование уникальных ключей, эти строки просто игнорируются. Аль-
тернатива такого поведения состоит в том, чтобы поместить в конце оператора IN-
SERT конструкцию ON DUPLICATE KEY UPDATE выражение. Это может использоваться
для изменения дублированного значения с помощью обычного оператора UPDATE (ко-
торый рассматривается далее в главе).
Чтобы сказанное стало понятнее, заполним базу данных рядом простых примеров
данных. Используемый для этого код представляет собой всего последовательность
простых операторов INSERT, в которых используется описанный выше подход вставки
нескольких строк. Сценарий, выполняющий эти операции, приведен в листинге 10.1.
Кроме того, его можно найти в файле \chapterlO\book_insert. sql на прилагаемом
компакт-диске.
Глаза 10. Работа с базой данных MySQL
263
Листинг 10.1. book insert. sql — SQL-код для заполнения данными таблиц БД “Буквофил”
use books;
insert into customers values
(3, "Julie Smith", "25 Oak Street", "Airport West"),
(4, "Alan Wong", "1/47 Haines Avenue", "Box Hill"),
(5, "Michelle Arthur", "357 North Road", "Yarraville");
insert into orders values
(NULL, 5, 69.98, "2005-06-02"),
(NULL, 3, 49.99, "2005-06-15"),
(NULL, 4, 74.98, "2005-06-19"),
(NULL, 5, 24.99, "2005-07-01");
insert into books values
("0-672-31697-8”, "Michael Morgan", "Java 2 for Professional Developers", 34.99),
("0-672-31745-1", "Thomas Down", "Installing Debian GNU/Linux", 24.99),
("0-672-31509-2", "Pruitt, et al.", "Teach Yourself GIMP in 24 Hours", 24.99),
("0-672-31769-9", "Thomas Schenk", "Caldera OpenLinux System Administration
Unleashed", 49.99);
insert into order_items values
(1, "0-672-31697-8", 2),
(2, ”0-672-31769-9", 1),
(3, "0-672-31769-9”, 1),
(3, "0-672-31509-2", 1),
(4, "0-672-31745-1", 3);
insert into book_reviews values
("0-672-31697-8", "Книга Моргана написана исключительно понятно и по праву
может считаться одной из лучших базовых книг, посвященных Java.");
Сценарий можно выполнить, запустив его через MySQL следующим образом:
> mysql -h host -u bookorama -p < book_insert.sql
Извлечение данных из базы данных
Оператор SELECT является в MySQL настоящей “рабочей лошадкой”. Он извлекает
данные из базы данных, выбирая из таблицы строки, которые отвечают заданному
критерию поиска. Существует множество опций и вариантов использования опера-
тора SELECT.
Основная же его форма выглядит следующим образом:
SELECT [options] items
[ INTO file_details ]
FROM tables
[ WHERE condition ]
[ GROUP BY group., type ]
[ HAVING where_definition ]
[ ORDER BY order_type ]
[ LIMIT limit_criteria ]
[ PROCEDURE proc_name(arguments) ]
[ lock—options ]
264
Часть II. Использование MySQL
В последующих разделах каждая конструкция описывается по отдельности. Одна-
ко вначале рассмотрим запрос без каких-либо дополнительных конструкций, когда
требуется просто выбрать определенные элементы из конкретной таблицы. Обычно
такими элементами являются столбцы таблиц. (Кроме того, они могут быть результа-
тами вычисления любых MySQL-выражений. Некоторые из наиболее полезных вы-
ражений рассматриваются далее в главе.) Следующий запрос выводит содержимое
столбцов паше и city таблицы customers:
select name, city
from customers;
Если данные были введены за счет выполнения сценария из листинга 10.1 и ос-
тальных двух примеров операторов INSERT, результат запроса будет следующим:
name -+ I city
Julie Smith | Airport West
Alan Wong | Box Hill
Michelle Arthur | Yarraville
Melissa Jones | Nar Nar Goon North
Michael Archer | Leeton - +
Как видите, получена таблица с выбранными элементами — name (ФИО) и city
(город) — из указанной таблицы клиентов customers. Эти данные собраны со всех
строк таблицы customers.
Из таблицы можно выбирать любое количество столбцов, помещая их список по-
сле ключевого слова select. Кроме того, в операторе можно указывать и другие эле-
менты. Весьма полезна групповая операция *, которая соответствует всем столбцам
указанной таблицы (или таблиц). Например, чтобы извлечь все столбцы и строки из
таблицы order_iterns, можно воспользоваться следующим оператором:
select *
from order_iterns;
в результате выполнения которого будет получен такой вывод:
+-
orderid | isbn quantity
1 1 0-672-31697-8 2
2 I 0-672-31769-9 1
3 | 0-672-31769-9 1
3 i 0-672-31509-2 1
4 1 +- 0-672-31745-1 3
Извлечение данных по определенному критерию
Чтобы получить доступ к подмножеству строк в таблице, необходимо задать кри-
терий выбора. Для этого можно воспользоваться конструкцией where.
Глава 10. Работа с базой данных MySQL
265
Например, оператор
select *
from orders
where customerid = 5;
выбирает все столбцы из таблицы заказов, но только из строк, в которых значение
customerid равно 5. В результате будет получен следующий вывод:
| orderid | customerid | amount date
1 I 5 | 69.98 | 2005-06-02 |
4 I 5 I 24.99 | 2005-07-01 |
Конструкция where устанавливает критерий выбора определенных строк. В нашем
случае выбраны строки, в которых столбец orderid содержит значение, равное 5.
Одиночный знак равно (=) используется для проверки на равенство — обратите вни-
мание, что этот синтаксис несколько отличается от синтаксиса РНР, и если работать
и с тем, и с другим, вполне можно запутаться, потому-то и не стоит терять внимание.
В дополнение к равенству MySQL, поддерживает целое семейство операций срав-
нения и регулярных выражений. Те из них, которые используются в конструкции
where наиболее часто, перечислены в табл. 10.1.
Таблица 10.1. Полезные операции сравнения, используемые в конструкциях WHERE
Операция Название (если применимо) Пример Описание
равно customerid = 3 Проверяет, являются ли два значения равными.
> больше amount > 6 С . 0 0 Проверяет, больше ли одно значение другого.
< меньше amount < 60.00 Проверяет, меньше ли одно значение другого.
>= больше или равно amount >= 60.00 Проверяет, больше или равно одно значе- ние по отношению к другому.
<= меньше или равно amount <-- 60.00 Проверяет, меньше или равно одно значе- ние по отношению к другому.
! = юи <> IS NOT NULL IS NULL BETWEEN IN NOT IN не равно quantity != 0 address is not null address is null amount between 0 and 60.00 city in ("Carlton", "Moe") city not in ("Carlton", "Moe") Проверяет, не равны ли два значения. Проверяет, содержит ли поле значение. Проверяет, что поле не содержит значения. Проверяет, является ли значение большим или равным минимальном}' и меньшим или равным максимальному. Проверяет, содержится ли значение в оп- ределенном наборе. Проверяет, не содержится ли значение в определенном наборе.
LIKE сопоставление с шаблоном name like ("Fred %") Используя простое сопоставление значе- ния с SQL-шаблоном, проверяет, соответ- ствует ли значение шаблону.
NOT LIKE сопоставление с шаблоном name not like ("Fred %") Проверяет, что значение не соответствует шаблону’.
REGEXP регулярное выражение name regexp Проверяет, соответствует ли значение регулярному выражению.
266
Часть II. Использование MySQL
Этот список далеко не полон, и если понадобится что-нибудь, отсутствующее в
нем, обратитесь к руководству по MvSQL.
Три последних строки табл. 10.1 относятся к LIKE и REGEXP. Это формы установки
соответствия шаблону.
Операция LIKE использует простое сопоставление с SQL-шаблоном. Шаблоны мо-
гут состоять из обычного текста и группового символа % (знак процента), указываю-
щего соответствие с любым количеством символов, или _ (символ подчеркивания),
указывающего соответствие с одним символом.
Ключевое слово REGEXP служит для сопоставления с регулярными выражениями.
В MySQL используются регулярные выражения в стиле POSIX. Вместо REGEXP можно
применять также ключевое слово RLIKE, являющееся его синонимом. Регулярные вы-
ражения POSIX также присутствуют и в РНР. Подробнее о них можно узнать в главе 4.
Можно проверять несколько критериев сразу с использованием простых опера-
ций и синтаксиса сопоставления с шаблоном, после чего комбинировать их более
сложный критерий с помощью операций AND и OR. Например:
select *
from orders
where customerid = 3 or customerid = 4;
Извлечение данных из нескольких таблиц
Часто для получения ответа от базы данных могут потребоваться данные из не-
скольких таблиц. Например, если необходимо узнать, кто из клиентов сделал заказы в
течение данного месяца, придется просмотреть таблицы customers и orders. Если
нужно узнать также, что конкретно они заказали, придется просмотреть и таблицу
order_iterns.
Эти данные хранятся в разных таблицах, поскольку относятся к разным реальным
объектам. Это один из принципов хорошего проекта базы данных, о которых шла
речь в главе 8.
Для получения информации подобного рода в SQL необходимо выполнить опера-
цию, называемую соединением (join). Это означает объединение двух и более таблиц в
соответствии с отношениями между данными. Если, например, необходимо посмот-
реть, какие заказы сделала наша хорошая клиентка Джули Смит (Julie Smith), вначале
потребуется просмотреть таблицу customers и найти в ней идентификатор клиента
Джули Смит (т.е. customerid), а затем — таблицу orders на предмет заказов, сделан-
ных клиентом с данным идентификатором customerid.
Хотя на первый взгляд операция соединения достаточно проста, на самом деле
это один из наиболее сложных и тонких аспектов SQL. В MySQL реализовано не-
сколько разных типов соединения, каждый из которых предназначен для определен-
ных целей.
Простое соединение двух таблиц
Начнем с поиска уже неоднократно упоминавшейся Джули Смит (Julie Smith):
select orders.orderid, orders.amount, orders.date
from customers, orders
where customers.name = 'Julie Smith'
and customers.customerid = orders.customerid;
Глава 10. Работа с базой данных MySQL
267
Результат запроса будет таким:
| orderid | amount | date |
ч----------ч---------ч-------------ч-
| 2 | 49.99 | 2005-06-15 |
Здесь следовало бы отметить несколько моментов.
Во-первых, для ответа на этот запрос необходима информация из двух таблиц, по-
этому в списке присутствуют обе.
Перечисляя две таблицы, вы также указываете тип соединения, возможно, даже
не зная его. Запятая между названиями таблиц эквивалентна конструкциям INNER
JOIN (внутреннее соединение) или CROSS JOIN (перекрестное соединение). Такой тип со-
единения еще называют полным соединением (full join) или декартовым произведением
(Cartesian product) таблиц. Это означает следующее: “Взять указанные таблицы и сде-
лать из них одну большую. Большая таблица должна содержать строку для любой воз-
можной комбинации строк из каждой указанной в списке таблицы, независимо от
того имеют они смысл или нет”. Другими словами, мы получаем таблицу, в которой
каждая строка таблицы customers сопоставляется каждой строке таблицы orders
независимо от того, какие заказы были сделаны конкретными клиентами.
В большинстве случаев такое соединение не имеет большого смысла. Как правило,
нам требуются строки, которые действительно совпадают, то есть когда конкретные
заказы соответствуют клиентам, которыми они были сделаны.
Это достигается путем помещения в конструкцию WHERE условия, соединения. Это
особый тип условного оператора, который поясняет, какие атрибуты отражают от-
ношения между двумя таблицами. В данном случае наше условие соединения было
таким:
customers.customerid = orders.customerid
что предписывает MySQL помещать в результирующую таблицу только те строки, для
которых customerid из таблицы customers совпадает с customerid из таблицы orders.
Внеся это условие в запрос, мы, по сути, получили соединение другого типа — со-
единение по равенству (equi-join).
Обратите внимание на точечную нотацию, которой мы воспользовались для
уточнения конкретного столбца конкретной таблицы. Так, customers . customerid
относится к столбцу customerid из таблицы клиентов customers, a orders. customerid —
к столбцу customerid из таблицы заказов orders.
Потребность в точечной нотации возникает, когда имена столбцов неоднознач-
ны, что случается, если одни и те же имена встречается в нескольких таблицах.
Расширив эту концепцию, точечную нотацию можно использовать для различе-
ния имен столбцов из разных баз данных. В приведенном примере используется за-
пись в форме таблица . столбец.
С помощью записи база_данных . таблица . столбец можно указать базу данных,
например, для проверки условия наподобие:
books.orders.customerid = other_db.orders.customerid
268
Часть II. Использование MySQL
С другой стороны, точечную нотацию можно применять и для всех ссылок на
столбцы в запросе. Часто это может оказаться целесообразным, особенно когда за-
просы становятся все сложнее и сложнее. MySQL этого не требует, но такая запись
делает запросы значительно более читабельными и удобными в использовании. Об-
ратите внимание, что мы придерживались этого соглашения в остальной части ранее
приведенного запроса - например, при указании следующего условия:
customers.name = 'Julie Smith'
Столбец name присутствует только в таблице customers, поэтому его необязатель-
но указывать. Но так, в общем-то, понятнее.
Соединение трех и более таблиц
Соединение большего количества таблиц не сложнее соединения двух таблиц.
Главное правило таково — таблицы необходимо объединять попарно, учитывая
условия соединения. Это можно представить в виде отношений между таблицами
в каждой такой паре.
Например, если требуется узнать, кто из клиентов заказал книги по Java (возможно,
с целью того, чтобы отправить им информацию о новых книгах данной тематики),
нужно отследить эти отношения в рамках сравнительно небольшого числа таблиц.
Необходимо будет отыскать клиентов, разместивших, по крайней мере, один
заказ, который содержит order_item, соответствующий книге по Java. Чтобы из
таблицы customers перейти к таблице orders, можно воспользоваться полем
customerid, как это делалось ранее. Чтобы из таблицы orders перейти в таблицу
order_iterns, можно воспользоваться полем orderid. Чтобы из таблицы order_items
перейти к конкретной книге в таблице Books, можно использовать поле ISBN. После
того как все связи установлены, можно запросить книги со словом “Java” в названии и
получить имена клиентов, которые купили какую-то из этих книг.
Давайте посмотрим на запрос, выполняющий все только что описанные действия:
select customers.name
from customers, orders, order_items, books
where customers.customerid = orders.customerid
and orders.orderid = order_items.orderid
and order_items.isbn = books.isbn
and books.title like '%Java%';
Этот запрос возвратит следующий результат:
+----------------+
| name |
+----------------+
| Michelle Arthur |
Обратите внимание, что в этом примере были отслежены данные из четырех раз-
ных таблиц, а чтобы сделать это с помощью соединения по равенству, понадобились
три разных условия соединения. Обычно каждой паре таблиц требуется одно условие
соединения. Таким образом, количество условий соединения на единицу меньше ко-
личества объединяемых таблиц. Это основное правило может пригодиться при от-
ладке запросов, которые работают неустойчиво. Проверьте условия соединения и
Глава 10. Работа с базой данных MySQL
269
убедитесь в том, что последовательность связей отражает путь от уже известного к
тому, что нужно узнать.
Поиск несовпадающих строк
Другой распространенный в MySQL тип соединения — левостороннее соединение
(left join).
В предыдущих примерах отбирались только те строки, в которых наблюдалось со-
ответствие между таблицами. Однако могут потребоваться и строки, в которых соот-
ветствие отсутствует, — например, нужно найти клиентов, которые не сделали пи од-
ного заказа, или книги, которые вообще никто не заказывал.
Самый простой вариант ответа на такой вопрос в MySQL предполагает использова-
ние левостороннего соединения, при котором выполняется поиск строк по указанному
условию соединения двух таблиц. Если в указанной справа таблице нет подходящей стро-
ки, к результату добавляется строка с нулевыми значениями в соответствующих столбцах.
Рассмотрим пример:
select customers.customerid, customers.name, orders.orderid
from, customers left join orders
on customers.customerid = orders.customerid;
Данный запрос SQL использует левостороннее соединение для таблиц customers
и orders. Его синтаксис в отношении условий соединения несколько иной; условие
соединения указывается в специальной конструкции ON SQL-оператора.
Вот как выглядит результат запроса:
I customerid | name | orderid |
1 1 1 Melissa Jones | NULL i
i 2 | Michael Archer | NULL |
i з Julie Smith | 2 1
1 4 Alan Wong | 3 i
1 5 | Michelle Arthur | 1 1
1 5 Michelle Arthur | 4 1
Из результата видно, что для клиентов Мелиссы Джонс (Melissa Jones) и Майкла
Арчера (Michael Archer) нет соответствующих идентификаторов заказа, поскольку их
поля orderid имеют значения NULL.
Если необходимо найти только тех клиентов, которые ничего не заказывали, это-
го можно достичь, выполнив проверку7 на наличие значений NULL в поле первичного
ключа правой таблицы (в данном случае, orderid), так как строки с реальными зна-
чениями не могут содержать значение NULL:
select customers.customerid, customers.name
from customers left join orders
using (customerid)
where orders.orderid is null;
Результат этого запроса выглядит следующим образом:
270
Часть II. Использование MySQL
| customerid | name |
| 1 | Melissa Jones |
| 2 | Michael Archer I
+------------+-------------------------------------------------------------+
Вероятно, вы обратили внимание на то, что в этом примере мы использовали не-
сколько иной синтаксис условия соединения. Левостороннее соединение поддержи-
вает как синтаксис ON, который был использован в первом примере, так и синтаксис
USING, который применялся во втором. Синтаксис USING не предполагает указания
таблицы, из которой поступает атрибут соединения, поэтому, чтобы его можно было
использовать, столбцы в обеих таблицах должны называться одинаково.
Вопрос подобного рода можно задать базе данных и с помощью подзапросов, ко-
торые рассматриваются далее в этой главе.
Использование других имен таблиц: псевдонимы
Часто бывает очень удобно, а порой и необходимо, обращаться к таблицам под
другими именами. Такие имена называются псевдонимами (alias). Их можно создать в
самом начале запроса, а затем использовать по мере необходимости. Часто псевдо-
нимы очень удобно применять в качестве кратких имен. Взгляните, как выглядит
достаточно громоздкий рассмотренный нами ранее запрос, переписанный с исполь-
зованием псевдонимов:
select с.name
from customers as c, orders as о, order_items as oi, books as b
where c.customerid = o.customerid
and o.orderid = oi.orderid
and oi.isbn = b.isbn
and b.title like '%Java%';
Для присвоения псевдонима таблице используется конструкция AS. Кроме того,
псевдонимы можно присваивать столбцам, однако к этому' мы вернемся после того,
как рассмотрим функции агрегирования.
Псевдонимы таблиц необходимы в случае соединения таблицы с самой собой. На
первый взгляд, такая операция кажется более сложной и загадочной, чем это есть на
самом деле. Тем не менее, такой подход очень удобен для поиска в одной и той же
таблице строк, содержащих одинаковые значения. Если требуется найти клиентов,
живущих в одном городе — скажем, с целью создания читательского клуба — одной и
той же таблице (customers) можно присвоить два разных псевдонима:
select cl.name, c2.name, cl.city
from customers as cl, customers as c2
where cl.city = c2.city
and cl.name != c2.name;
Мы делаем вид, что таблица customers — это две разных таблицы cl и с2, и вы-
полняем их соединение по столбцу City. Второе условие, cl.name != с2.name, не-
обходимо для предотвращения сопоставления клиента самому себе.
Глава 10. Работа с базой данных MySQL
271
Резюме по типам соединений
В табл. 10.2 перечислены различные рассмотренные ранее типы соединений. Су-
ществуют также несколько других типов, однако приведенные в табл. 10.2 соедине-
ния используются наиболее часто.
Таблица 10.2. Типы соединений в MySQL
Название Описание
Декартово Все комбинации всех строк во всех таблицах. В случае применения
произведение между именами таблиц ставят запятые и не используют конструк- цию WHERE.
Полное соединение Аналогично предыдущему.
Перекрестное Аналогично предыдущему. Также может использоваться с указанием
соединение ключевых слов CROSS JOIN между названиями объединяемых таблиц.
Внутреннее Семантически эквивалентно запятой. Может использоваться с ука-
соединение занием ключевых слов INNER JOIN. Без условия WHERE эквивалентно полному соединению. Обычно при истинно внутреннем соединении задается условие WHERE.
Соединение Использует условное выражение со знаком = для сопоставления в
по равенству соединении строк из различных таблиц. В SQL в этом соединении применяется конструкция WHERE.
Левостороннее Предпринимает попытку сопоставить строки в таблицах и заполни-
соединение ет несовпадающие строки значениями NULL. В SQL используется с ключевыми словами LEFT JOIN. Предназначено для поиска отсутст- вующих значений. Аналогично можно употреблять RIGHT JOIN.
Извлечение данных в определенном порядке
Если извлеченные по запросу строки должны перечисляться в определенном по-
рядке, можно воспользоваться конструкцией ORDER BY оператора SELECT. Эта осо-
бенность удобна для представления результатов запроса в удобочитаемом формате.
Конструкция ORDER BY применяется для сортировки строк в столбцах, указанных
в операторе SELECT. Например:
select name, address
from customers
order by name;
Такой запрос выведет имена и адреса клиентов в алфавитном порядке по именам:
+ | name - + | address - +
| Alan Wong | 1/47 Haines Avenue
| Julie Smith | 25 Oak Street
| Melissa Jones
| Michael Archer | 12 Adderley Avenue
| Michelle Arthur | 357 North Road 1
+ -+ - +
Обратите внимание, что в данном случае, поскольку имена состоят из собственно
имени и фамилии, они будут упорядочены по имени. Если требуется выполнить сор-
272
Часть II. Использование MySQL
тировку по фамилии (которая стоит второй), нужно, чтобы имя и фамилия храни-
лись в двух различных полях.
По умолчанию используется порядок сортировки по возрастанию (от а до z или в
порядке возрастания числовых значений). При желании этот порядок сортировки
можно указать ключевым словом ASC:
select name, address
from customers
order by name asc;
Изменить порядок сортировки на обратный можно с помощью другого ключевого
слова — DESC:
select name, address
from customers
order by name desc;
Сортировать можно и по нескольким столбцам. Вместо названий можно исполь-
зовать псевдонимы столбцов, и даже их порядковые номера (например, 3 для третье-
го столбца в таблице).
Группировка и агрегирование данных
Нередко требуется узнать, сколько строк относится к определенному набору или
каково среднее значение какого-нибудь столбца — скажем, средняя стоимость одного
заказа в денежном выражении. В MySQL имеется набор функций агрегирования, ко-
торые неплохо подходят для выполнения задач подобного рода.
Эти функции агрегирования можно применять как к таблице в целом, так и к груп-
пам данных внутри таблицы.
Наиболее часто используемые функции перечислены в таблице 10.3.
Таблица 10.3. Функции агрегирования в MySQL
Название Описание
AVG(столбец) Средняя величина значений в определенном столбце.
COUNT(элементы) При указании столбца выдается количество ненулевых значений в этом столбце. Если перед названием столбца поместить слово DISTINCT, выдает ся только количество различных значений в столбце. Если указать COUNT (*) — подсчет строк будет производиться независимо от нулевых значений.
MIN(столбец} Минимальное значение в указанном столбце.
МАХ(столбец) Максимальное значение в указанном столбце.
STD(столбец) Стандартное отклонение значений в указанном столбце.
STDDEV(столбец) Аналогично предыдущему.
SUM(столбец) Сумма значений в указанном столбце.
Рассмотрим несколько примеров, начиная с упомянутого ранее. Среднюю сумму
заказа можно вычислить следующим образом:
select avg(amount)
from orders;
Глава 10. Работа с базой данных MySQL
273
Результат будет приблизительно таким:
| avg(amount) |
54.985002 |
Для получения более подробной информации можно воспользоваться конструк-
цией GROUP BY. Это позволит просмотреть среднюю сумм}’ заказа по группам, напри-
мер, по номеру клиента, что позволит выяснить, кто из клиентов делает самые круп-
ные заказы:
select customerid, avg(amount)
from orders
group by customerid;
При использовании конструкции GROUP BY в сочетании с функцией агрегирова-
ния это фактически изменяет поведение функции. Вместо того чтобы выдавать сред-
нюю сумм}’ всех заказов в таблице, такой запрос выведет информацию по средней
сумме заказов, сделанных каждым клиентом (а если точнее, каждым customerid):
| customerid | avg(amount) |
1 | 49.990002 |
2 | 74.980003 |
3 | 47.485002 j
При использовании функций группировки и агрегирования необходимо обратить
внимание на следующий момент: если функция агрегирования или конструкция
GROUP BY используется в ANSI SQL, в операторе SELECT могут присутствовать только
функции агрегирования и столбцы, указанные в конструкции GROUP BY. Кроме того,
если столбец требуется задействовать в конструкции GROUP BY, он должен присутст-
вовать в операторе SELECT.
На самом деле MySQL обеспечивает гораздо большую свободу действий, поддер-
живая расширенный синтаксис, который дает возможность убирать ненужные элемен-
ты из оператора SELECT.
Кроме группировки и агрегирования данных имеется возможность действительно
проверить результат агрегирования, используя конструкцию HAVING. Она следует
сразу после конструкции GROUP BY и подобна конструкции WHERE, однако применяет-
ся только к группам и совокупностям.
Чтобы расширить предыдущий пример, скажем, для получения информации о
том, кто из клиентов произвел заказ, средняя сумма которого превышает 50 долла-
ров, можно воспользоваться следующим запросом:
select customerid, avg(amount)
from orders
group by customerid
having avg(amount) > 50;
274
Часть II. Использование MySQL
Обратите внимание, что конструкция HAVING применяется к группам. Такой за-
прос приводит к получению следующего результата:
| customerid | avg(amount) |
+------------+--------------+
| 2 | 74.980003 |
Выбор возвращаемых строк
Конструкцией оператора SELECT, которая может оказаться особенно полезной в
Web-приложениях, является LIMIT. Ее используют для указания строк результата, ко-
торые должны быть возвращены. Она требует указания двух параметров: номера на-
чальной строки и количества возвращаемых строк.
Следующий запрос неплохо иллюстрирует применение LIMIT:
select name
from customers
limit 2, 3;
Этот запрос можно интерпретировать следующим образом: “Выбрать имена кли-
ентов, а затем отобразить 3 строки, начиная со строки 2”. Не забывайте, что нумера-
ция строк начинается с нуля.
Эта конструкция очень удобна для Web-приложений, например, когда покупатель
просматривает каталог, и необходимо, чтобы на каждой странице отображалось
только 10 пунктов. Обратите, однако, внимание, что LIMIT в стандарте ANSI SQL от-
сутствует. Это расширение MySQL, поэтому использование LIMIT приводит к несо-
вместимости кода со многими СУРБД.
Использование подзапросов
Подзапрос — это запрос, вложенный внутрь другого запроса. Механизм подзапро-
сов появился в версии MySQL 4.1. Несмотря на то что большая часть функционально-
сти подзапросов может быть реализована за счет использования соединений и вре-
менных таблиц, подзапросы намного легче читать и реализовывать.
Базовые подзапросы
Наиболее общим применением подзапросов можно считать случай, когда резуль-
тат одного запроса используется в операции сравнения, находящейся в другом запро-
се. Например, пусть необходимо найти заказ на максимальную сумму среди всех зака-
зов; оператор SELECT может выглядеть следующим образом:
select customerid, amount
from orders
where amount = (select max(amount) from orders);
Результат его выполнения показан ниже:
| customerid | amount |
+------------+--------+
| 4 | 74.98 |
Глава 10. Работа с базой данных MySQL
275
В этом случае подзапрос возвращает единственное значение (максимальную сумму
заказа), которая затем участвует в сравнении в рамках внешнего запроса. Это очень
хороший пример, поскольку запрос подобного рода не может быть элегантно реали-
зован в ANSI SQL.
Тот же самый вывод, однако, дает и запрос с соединением:
select customerid, amount
from orders
order by amount desc
limit 1;
Ввиду того, что приведенный запрос основан на LIMIT, он не совместим с боль-
шинством СУРБД, однако в MySQL он выполняется более эффективно, чем вариант с
подзапросом.
Одна из главных причин, почему механизм подзапросов так долго не появлялся в
MySQL, состоит в том, что существует очень мало вещей, которые можно выполнять
только с их помощью. С технической точки зрения, вы можете создать одиночный,
совместимый с ANSI SQL запрос, который дает тот же эффект, но основывается на
неэффективном трюкачестве, которое носит название MAX-CONCAT.
Значение, полученные из подзапроса, можно использовать во всех стандартных
операциях сравнения. Доступны также некоторые специальные операции сравнения
для подзапросов; они рассматриваются в следующем разделе.
Подзапросы и операции
Существуют пять специальных операций подзапросов. Четыре из них использу-
ются в обычных подзапросах, и одна (EXISTS) — как правило, только в связанных
(correlated) подзапросах, которые рассматриваются в следующем разделе. Четыре
обычных операции подзапросов описаны в табл. 10.4.
Таблица 10.4. Операции подзапросов
Название Пример синтаксиса Описание
ANY SELECT Cl FROM tl WHERE cl > ANY (SELECT cl FROM t2) ; Возвращает true, если сравнение ис- тинно для любой строки в подзапросе.
IN SELECT Cl FROM tl WHERE Cl IN (SELECT cl from t2); Эквивалентна =ANY.
SOME SELECT Cl FROM tl WHERE cl > SOME (SELECT cl FROM t2); Другое название ANY; иногда звучит приятнее для человеческих ушей.
ALL SELECT Cl FROM tl WHERE cl > ALL (SELECT cl from t2); Возвращает true, если сравнение ис- тинно для всех строк в подзапросе.
Каждая из этих операций может находиться только после операции сравнения, за
исключение IN, которая имеет свою операцию сравнения (=), “свернутую внутри”,
если можно так выразиться.
276
Часть II. Использование MySQL
Связанные подзапросы
Связанные подзапросы более сложны в понимании. В них элементы, полученные
во внешнем запросе, используются во внутреннем запросе. Например:
select isbn, title
from books
where not exists
(select * from order_items where order_items.isbn=books.isbn) ;
В приведенном запросе демонстрируется применение как связанных подзапросов,
так и специальной операции подзапросов EXISTS. Запрос извлекает все книги, кото-
рые никогда не были заказаны. (Выше в главе вы получали эту информацию с помо-
щью левого соединения.) Обратите внимание, что внутренний запрос включает таб-
лицу order_items только в список FROM, однако ссылается на books, isbn. Другими
словами, внутренний запрос ссылается на данные внешнего запроса. Это и есть опре-
деление связанного подзапроса: вы ищете строки внутреннего запроса, которые сов-
падают (или, как в рассмотренном примере, не совпадают) со строками внешнего
запроса.
Операция EXISTS возвращает true, если подзапрос содержит хоть одну совпа-
дающую строку. Соответственно, операция NOT EXISTS возвращает true, если подза-
прос не содержит совпадающих строк.
Строковые подзапросы
Все рассмотренные до сих пор подзапросы возвращали единственное значение,
которое в большинстве случаев равно true или false (как в предыдущем примере,
использующем EXISTS). Строковые подзапросы возвращают целую строку, которая
затем может сравниваться с целой строкой во внешнем запросе. В общем случае та-
кой подход используется в отношении строк одной таблицы, которые также сущест-
вуют в другой таблице. Хотя это и не очень хороший пример для базы данных books,
однако ниже показан обобщенный пример упомянутого синтаксиса:
select cl, с2, сЗ
from tl
where (cl, c2, сЗ) in (select cl, c2, c3 from t2> ;
Использование подзапроса как временной таблицы
Подзапрос можно использовать в конструкции FROM внешнего запроса. Этот под-
ход дает возможность выполнять запрос к выходным данным подзапроса, рассматри-
вая их как временную таблицу.
Ниже показан простой пример:
select * from
(select customerid, name from customers where city='Box Hili')
as box_hill_customers;
Обратите внимание, что мы поместили подзапрос в конструкцию FROM. Непосред-
ственно после закрывающей скобки подзапроса вы должны присвоить результату
подзапроса какой-то псевдоним. После этого с псевдонимом во внешнем запросе
можно работать как с любой другой таблицей.
Глава 10. Работа с базой данных MySQL
277
Обновление записей в базе данных
Помимо того, что данные необходимо извлекать из базы данных, очень часто их
нужно изменять. Например, иногда требуется повысить цены на книги в базе данных.
Это можно сделать с помощью оператора UPDATE.
Типичная форма этого оператора выглядит следующим образом:
UPDATE [LOW_PRIORITY] [IGNORE] tablename
SET columnl=expressionl, column2=expression2, ...
[WHERE condition]
[ORDER BY order_criteria]
[LIMIT number]
Основная идея заключается в обновлении таблицы с именем tablename путем ус-
тановки значения каждого указанного столбца columnl, column2 и так далее равным
определенному выражению expression!, expression^ и так далее. Работу оператора
UPDATE можно ограничить определенными строками, используя конструкцию WHERE и
ограничив общее количество строк, которые будут обновлены, с помощью конструк-
ции LIMIT. Конструкция ORDER BY обычно используется в связке с конструкцией
LIMIT; например, если необходимо обновить только первых 10 строк, то часто требу-
ется сначала расположить строки в определенном порядке. Необязательные конст-
рукции LOW_PRIORITY и IGNORE, если указаны, то работают точно так же, как в опера-
торе INSERT.
Рассмотрим несколько примеров.
Если нужно повысить цену абсолютно всех книг на 10%, можно воспользоваться
оператором UPDATE без конструкции WHERE:
update books
set price=price*l.1;
Если же требуется изменить одну строку, скажем, адрес определенного клиента,
можно поступить следующим образом:
update customers
set address = '250 Olsens Road1
where customerid = 4;
Изменение таблиц после создания
Помимо обновления строк, может потребоваться изменить структуру таблиц в ба-
зе данных. Для этого служит очень гибкий оператор ALTER TABLE. Его основная фор-
ма такова:
ALTER TABLE [IGNORE] tablename alteration [, alteration ...]
Обратите внимание, что в ANSI SQL один оператор ALTER TABLE может осущест-
вить только одно преобразование, а вот его MySQL-версия лишена подобных ограни-
чений. Для изменения различных аспектов таблицы можно использовать различные
конструкции преобразования.
Если необязательная конструкция IGNORE присутствует, то при попытке провести
изменение, которое вызывает дублирование первичных ключей, первая строка с таким
278
Часть II. Использование MySQL
ключом останется в изменяемой таблице, а остальные будут удалены. Если же IGNORE
не указана (по умолчанию это так), изменение завершается неудачей и выполняется
его откат.
Типы преобразований, осуществляемые оператором ALTER TABLE, перечислены в
табл. 10.5.
Таблица 10.5. Возможные преобразования, выполняемые оператором ALTER TABLE
Синтаксис Описание
ADD [COLUMN] column_description [FIRST I AFTER column ] Добавляет новый столбец в указанное место (если место не указано, столбец добавляется в конец). Обратите внимание, что column_description тре- бует указания имени и типа, точно так же, как опе- ратор CREATE.
ADD [COLUMN] (column_description, column_description, ...) Добавляет один или несколько столбцов в конец таблицы.
ADD INDEX [index] (column, ...) Добавляет индекс по указанному столбцу (столб- цам) таблицы.
ADD [CONSTRAINT [symbol]] PRIMARY KEY (column, ...) Делает указанный столбец (столбцы) первичным ключом таблицы. Конструкция CONSTRAINT приме- няется для таблиц с внешними ключами. Дополни- тельную информацию можно найти в главе 13.
ADD UNIQUE [CONSTRAINT [symbol]] [index] (column, ...) Добавляет уникальный индекс по указанному столбцу (столбцам) таблицы. Конструкция CONSTRAINT применяется для таблиц InnoDB с внешними ключами. Дополнительную информа- цию можно найти в главе 13.
ADD [CONSTRAINT [symbol]] FOREIGN KEY [index] (index_col, . ..} [reference_definlCion] Добавляет в таблицу InnoDB внешний ключ. Дополнительную информацию можно найти в главе 13.
ALTER [COLUMN] column {SET DEFAULT value | DROP DEFAULT] Добавляет или удаляет значение по умолчанию для определенного столбца.
CHANGE [COLUMN] column new_column_description Изменяет столбец с именем column так. в результате чего он получает указанное описание. Обратите внимание, что это можно использовать для изменения имени столбца, поскольку column_description включает в себя имя.
MODIFY [COLUMN] column_description Подобно CHANGE. Используется для изменения ти- пов столбцов, но не их имен.
DROP [COLUMN] column Удаляет столбец column.
DROP PRIMARY KEY Удаляет первичный индекс (но не столбец).
DROP INDEX index Удаляет индекс index.
DROP FOREIGN KEY key Удаляет внешний ключ key.
DISABLE KEYS Отключает обновление индексов.
ENABLE KEYS Включает обновление индексов.
RENAME [AS] new_table_name Переименовывает таблицу.
Глава 10. Работа с базой данных MySQL
279
Окончание табл. 10.5
Синтаксис Описание
ORDER BY col_name Повторно создает таблицу со строками в опреде- ленном порядке. (Обратите внимание, что после того, как изменение таблицы началось, строки больше не будут располагаться по порядку.)
CONVERT TO CHARACTER SET cs Преобразует все текстовые столбцы к указанному
COLLATE c набору символов и правилам сопоставления.
[DEFAULT] CHARACTER SET cs Устанавливает набор символов и правила сопос-
COLLATE c тавления по умолчанию.
DISCARD TABLESPACE Удаляет лежащий в основе файл табличного про- странства для таблицы InooDB. (Дополнительную информацию по InnoDB можно найти в главе 13.)
IMPORT TABLESPACE Повторно создает лежащий в основе файл табличного пространства для таблицы InooDB. (Дополнительную информацию по InnoDB можно найти в главе 13.)
table_options Позволяет сбросить опции таблицы. Использует тот же синтаксис, что и CREATE TABLE.
Рассмотрим наиболее типичные случаи употребления оператора ALTER TABLE.
Часто внезапно оказывается, что какой-то столбец “недостаточно велик”, чтобы
вместить в себе необходимые данные. Например, в нашей таблице customers имена и
фамилии могут иметь длину до 50 символов. Вскоре может оказаться, что некоторые
имена и фамилии слишком длинны и сохраняются в таблице в усеченном виде. По-
добную ситуацию можно легко исправить, изменив тип данных столбца, после чего
он сможет принимать имена и фамилии длиной, скажем, до 70 символов:
alter table customers
modify name char(70) not null;
Еще одна часто встречающаяся ситуация связана с необходимостью добавления
столбца. Представьте, что в каждом регионе существует свой налог с продаж, поэтому
магазину “Буквофил” приходится учитывать этот налог, но делать это раздельно. В
таблицу orders можно добавить столбец налога под названием tax:
alter table orders
add tax float(6,2) after amount;
Иногда какой-нибудь столбец может оказаться лишним. Только что добавленный
столбец можно удалить следующим образом:
alter table orders
drop tax;
Удаление записей из базы данных
Удалять строки из базы данных очень просто. Этом делается с помощью операто-
ра DELETE, который в общем случае выглядит следующим образом:
280
Часть II. Использование MySQL
DELETE [LOW-PRIORITY] [QUICK] [IGNORE] FROM table
[WHERE condition]
[ORDER BY order_cols]
[LIMIT number]
Если просто записать:
DELETE FROM table;
то это приведет к удалению всех строк в таблице, так что будьте предельно осторож-
ны! Обычно требуется удалить определенные строки, и их следует указывать с помо-
щью конструкции WHERE. Например, подобная ситуация может возникнуть, если ка-
кая-то книга больше не продается или кто-то из клиентов длительное время ничего не
заказывает:
delete from customers
where customerid=5;
Конструкцию LIMIT можно использовать для ограничения максимального количе-
ства в действительности удаляемых строк. Конструкция ORDER BY обычно использу-
ется вместе с LIMIT.
Конструкции LOW-PRIORITY и QUICK работают обычным образом. QUICK может ус-
корить выполнение этого оператора на таблицах MylSAM.
Удаление таблиц
Временами возникает необходимость избавиться от целой таблицы. Это можно
сделать с помощью оператора DROP TABLE. Его синтаксис исключительно прост:
DROP TABLE table;
Он удаляет все строки из таблицы и саму таблицу, так что будьте с ним поаккуратней.
Удаление целой базы данных
Можно пойти еще дальше и удалить целую базу данных, используя для этого опе-
ратор DROP DATABASE:
DROP DATABASE database;
В результате удаляются все строки, таблицы, индексы и сама база данных. Не сто-
ит и говорить, что при использовании этого оператора нужно соблюдать предельную
осторожность.
Дополнительные источники информации
В этой главе было рассмотрено то подмножество языка SQL, с которым обычно
приходится иметь дело при работе с базами данных MySQL. В последующих двух гла-
вах мы объясним, как объединить MySQL с РНР, чтобы получать доступ к базе данных
из Web. Кроме того, будут рассмотрены некоторые более сложные технологии
MySQL.
Глава 10. Работа с базой данных MySQL
281
Если необходима дополнительная информация по SQL, имеет смысл обратиться к
описанию стандарта ANSI SQL, которое доступно по адресу:
http://www.ansi.org/
Описание расширений MySQL по сравнению с ANSI SQL можно найти на Web-
сайте MySQL по адресу http: / /www.mysql. сот/.
Что дальше
В главе 11 будет показано, как получить доступ к базе данных приложения “Букво-
фил” через Web.
282
Часть II. Использование MySQL
11
Доступ к базе данных
MySQL из Web
с помощью РНР
До сих пор при работе с РНР для хранения и извлечения данных мы использова-
ли двумерные файлы. Когда этот вопрос рассматривался в главе 2, говорилось о
том, что применение систем реляционных баз данных в Web-приложении обеспечи-
вает значительно более простое, безопасное и эффективное выполнение задач хра-
нения и извлечения данных. Теперь, поработав с MySQL над созданием базы данных,
можно приступать к подключению этой базы данных к внешнему Web-интерфейсу.
В этой главе будет показано, как с помощью РНР получить доступ к БД “Буквофил”
через Web. Вы научитесь выполнять чтение из базы и запись в нее данных, а также
отфильтровывать потенциально опасные входные данные.
В главе, помимо прочих, рассматриваются следующие темы:
Как работает архитектура баз данных для Web.
Основные шаги выполнения запросов к базе данных из Web.
Установка соединения.
Получение информации о доступных базах данных.
Выбор базы данных.
Выполнение запроса к базе данных.
Получение результатов запроса.
Отсоединение от базы данных.
Внесение новой информации в базу данных.
Использование подготовленных операторов.
Использование других PHP-интерфейсов работы с базами данных.
Использование обобщенного интерфейса базы данных: PEAR DB.
Как работает архитектура баз данных д ля Web
В главе 8 мы в общих чертах выяснили, как работает архитектура баз данных для
Web. Еще раз напомним шаги, предпринимаемые в ходе работы:
1. Web-браузер пользователя отправляет HTTP-запрос определенной Web-страни-
цы. Например, может быть выдан запрос на поиск в магазине “Буквофил” всех
книг, написанных Лорой Томсон (Laura Thomson), с использованием HTML-
формы. Страница с результатами поиска называется results.php.
2. Web-сервер принимает запрос на results.php, извлекает файл и передает его
на обработку механизму РНР.
3. Механизм РНР начинает синтаксический анализ сценария. Сценарий содер-
жит команду подключения к базе данных и выполнения запроса (на поиск
книг). РНР открывает соединение с сервером MySQL и отправляет ему' соот-
ветствующий запрос.
4. Сервер MySQL принимает запрос базы данных, обрабатывает его, а затем от-
правляет результаты — в данном случае, список книг — обратно механизму РНР.
5. Механизм РНР завершает выполнение сценария, что обычно сопряжено с
форматированием результатов запроса в виде HTML, после чего возвращает
результаты в HTML-формате Web-серверу.
6. Web-сервер пересылает браузеру HTML-страницу, в которой пользователь мо-
жет просмотреть список необходимых книг.
Теперь мы располагаем базой данных MySQL, поэтому можем создать PHP-код для
выполнения описанных шагов. Начнем с поисковой формы. Это простая HTML-
форма, код которой приведен в листинге 11.1.
Листинг 11.1. search. html — поисковая страница для БД “Буквофил”
<html>
<head>
<Б1б1е>Магазин "Буквофил" - Поиск в каталоге</б1б1е>
</head>
<body>
<Ы>Магазин "Буквофил" - Поиск в каталоге</111>
<form action=”results.php" method="post">
Выберите тип поиска:<br />
<select name="searchtype">
<option value="author“>По aBTopy</option>
<option value=“title">no названию</орЬ1оп>
<option value="isbn">no ISBN</option>
</select>
<br />
Введите информацию для поиска:<br />
<input name="searchterm" type="text">
<br />
cinput type="submit" value=”Найти">
</form>
</body>
284
Часть II. Использование MySQL
</html>
Как уже упоминалось, эта HTML-форма является достаточно простой. Ее внешний
вид показан на рис. 11.1.
Магазин ’‘Буквофил11 - Поиск в яа&- - . -
Файл Правка Вид Переход Закладки Инструменты Окно Сграека Debug Qft
! f* >Tt1p7/localhosVphpmysql3e/chaptErll/search html Поиск'
Нззэд Вперед Обновить —
jj Магазин "Буквофил" - Поиск в каталоге
; • Выберите тип поиска
! По автору
! Введите информацию для поиска.
; -Michael Morgan
Найти I
Готово .-
Рис. 11.1. Поисковая форма является достаточно общей, то есть
книгу можно искать по названию, автору или номеру ISBN
После щелчка на кнопке Найти вызывается сценарий results .php. Его полный код
показан в листинге 11.2. В данной главе мы рассмотрим, что конкретно делает этот
сценарий и как он работает.
Листинг 11.2. results .php — извлекает результаты запроса из базы данных MySQL
и форматирует их для отображения
<html>
<head>
<title>Mara3MH "Буквофил" - Результаты noncKa</title>
</head>
<body>
<Ы>Магазин "Буквофил" - Результаты поиска</Ы>
<?php
// создание коротких имен переменных
$searchtype=$_POST['searchtype'];
$searchterm=$_POST['searchterm'];
$searchterm= trim($searchterm);
if (!$searchtype || I$searchterm)
(
echo 'Вы не ввели параметры поиска. Пожалуйста, вернитесь на предыдущую
страницу и повторите ввод.';
exit;
}
if (!get_magic_quotes_gpc())
(
$searchtype = addsla^hes($searchtype);
$searchterm = addslashes(Ssearchterm);
}
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
285
@ Sdb = new mysqli('localhost', ’bookorama’, 'bookoramal23', ’books');
if (mysqli_connect_errno())
{
echo ’Ошибка: He удалось установить соединение с базой данных.
Пожалуйста, повторите попытку позже.';
exit;
}
$query = "select * from books where ''. $searchtype. ” like ' %". $searchterm."%' " ;
$result = $db->query(Squery);
$num_results = $result->num_rows;
echo '<р>Найдено книг: '.$num_results.’</p>“;
for ($i = 0; $i < $num_results; $i++)
{
$row = $result->fetch_assoc();
echo ’<pxstrong>' . ($i+l) . ' . Название: ';
echo htmlspecialchars (scripslashes($row['tide']));
echo ’</strongxbr />Автор: ' ;
echo stripslashes($row['author']);
echo “<br />ISBN: ";
echo stripslashes($row['isbn']);
echo '<br />Цена: ';
echo stripslashes($row['price']);
echo '</p>’;
}
$result->free() ;
$db->close();
</body>
</html>
Обратите внимание, что сценарий позволяет вводить групповые символы MySQL: %
и _ (подчеркивание). Эта возможность исключительно полезна для пользователей. Ес-
ли упомянутые символы создают проблемы в вашем приложении, можете отменить их.
Результат использования этого сценария для выполнения поиска показан на
рис. 11,2.
286
Часть II. Использование MySQL
Рис. 11.2. Результаты поиска в базе данных книг по Java с помощью
сценария results .php, представленные в виде Web-страницы
Выполнение запросов к базе данных из Web
В любом сценарии, который обеспечивает доступ к базе данных из Web, необхо-
димо выполнить ряд базовых шагов:
1. Проверить и отфильтровать данные, поступающие от пользователя.
2. Установить соединение с требуемой базой данных.
3. Реализовать запрос к базе данных.
4. Получить результаты.
5. Представить результаты пользователю.
Именно эти действия и выполняет сценарий results .php, и мы по очереди иссле-
дуем каждое из них.
Проверка и фильтрация входных данных
Прежде всего, необходимо удалить все лишние пробелы, которые пользователь
мог случайно ввести перед или после критерия поиска. Это делается с помощью
функция trim(), применяемой к $searchterm.
$searchterm = trim($searchterm);
Следующий этап связан с проверкой того, что пользователь указал критерий и тип
поиска. Обратите внимание, что она выполняется лишь после удаления лишних про-
белов по краям критерия поиска. Если поменять эти действия местами, может воз-
никнуть ситуация, когда критерий не был пустым и поэтому не привел к выводу со-
общения об ошибке, но при этом содержал только пробелы, которые полностью
удаляются функцией trim ():
if {!$searchtype || !Ssearchterm)
{
echo 'Вы не ввели параметры поиска. Пожалуйста, вернитесь на предыдущую
страницу и повторите ввод.';
exit;
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
287
Была выполнена проверка переменной $ searchtype несмотря на то. что в данном
случае она поступает из HTML-дескриптора SELECT. Может возникнуть вопрос, зачем
нужно проверять входные данные. Не забывайте, что с базой данных может быть свя-
зан далеко не один интерфейс. Например, компания Amazon располагает большим
количеством филиалов, которые используют свои поисковые интерфейсы. Кроме
того, важно защитить данные на случай возникновения каких-либо проблем безопас-
ности, связанных с тем, что пользователи могут заходить с разных рабочих станций.
И еще, если предполагается ввод пользователем каких-то данных, важно исклю-
чить из них любые управляющие символы. Как вы. наверное, помните, в главе 4 го-
ворилось о функциях addslashes (). stripslashes () и get_magic_quotes_gpc . Вы
должны отменить управляющие символы (литерализировать их) в данных, введен-
ных пользователем, перед сохранением их в базе.
В нашем случае осуществляется проверка значения, возвращаемого функцией
get_magic_quotes_gpc(). Это значение указывает, выполняется ли автоматическое
взятие в кавычки. Если это не так, с помощью функции addslashes () отменяются
управляющие символы:
if (!get_magic_quotes_gpc())
{
$searchtype = addslashes($searchtype);
$searchterm = addslashes($searchterm);
}
Функция htmlspecialchars() используется для кодировки символов, которые
имеют специальное значение в HTML. Наши тестовые данные не содержат символов
амперсанда (&), знаков "меньше” (<), “больше” (>) или двойных кавычек ("). однако
амперсанд может встречаться в названиях многих замечательных книг. Использова-
ние этой функции поможет избежать ошибок в будущем.
Установка соединения
В РНР5 имеется новая библиотека для подключения к MySQL, называемая mysqli
(“i” означает “improved” — “улучшенная”). Эта библиотека рассчитана на работу с вер-
сией MySQL 4 и более поздними. В MySQL 4 появился новый быстрый протокол под-
ключения, и mysqli позволяет задействовать все его преимущества. Кроме того, биб-
лиотека mysqli позволяет использовать два синтаксиса: объектно-ориентированный и
процедурный.
Для соединения с сервером MySQL служит следующая строка сценария:
@ $db = new mysqli('localhost', 'bookorama', 'bookoramal23', 'books');
В этой строке создается экземпляр класса mysqli и предпринимается попытка
соединения с хостом 'localhost’ с использованием имени 'bookorama' и пароля
' bookoramal2 3 '. Соединение будет использовать базу данных books.
В соответствие с объектно-ориентированным подходом, вы вызываете методы
полученного объекта для доступа к базе данных. Если вы предпочитаете процедурный
подход, mysqli допускает и его. В этом случае подключение будет выглядеть следую-
щим образом:
288
Часть II. Использование MySQL
® $db = mysqli_connect(1 localhost1, 'bookorama', 'bookoramal23', ‘books');
Приведенная функция возвращает ресурс, а не объект. Этот ресурс представляет
собой соединение с базой данных, и при использовании процедурного подхода вы
должны передавать его во все остальные функции mysqli. Данный подход очень по-
хож на работу с файловыми функциями наподобие fopen ().
Большинство функций mysqli имеют и объектно-ориентированный, и процедур-
ный интерфейс. В общем случае, отличия между ними заключаются в том, что имена
процедурных вариантов функций начинаются с префикса mysql i_, и им необходимо
передавать ресурс, который получен в результате вызова функции mysqli_connect ().
Исключением из этих правил является подключение к базе данных, так как оно вы-
полняется конструктором класса mysql 1.
Результат попытки подключения должен быть проверен, поскольку остальной код
в случае неудачного подключения работать не будет. Проверка осуществляется с по-
мощью следующего кода:
if (mysqli_connect_errno())
{
echo 'Ошибка: He удалось установить соединение с базой данных.
Пожалуйста, повторите попытку позже.';
exit ;
)
(Здесь также существуют обе версии: объектно-ориентированная и процедурная.)
Функция mysqli__connect_errno () возвращает номер ошибки либо 0 в случае удачно-
го подключения.
Обратите внимание, что строка, в которой выполняется попытка подключения к
базе данных, начинается с операции подавления ошибок @. В этом случае можно ор-
ганизовать элегантную обработку ошибочных ситуаций. (Обработку ошибок можно
также реализовать и с помощью исключений, однако в данном простом примере бы-
ло решено их не использовать.)
Следует помнить, что в MySQL количество соединений, которые могут суще-
ствовать одновременно, ограничено. Этот предел определяется параметром
max_connections. Его назначение (как и родственного ему параметра MaxClients из
Web-сервера Apache) — вынудить сервер отклонять новые запросы на соединение,
чтобы не допустить полного использования ресурсов компьютера в часы наивысшей
загрузки или при аварии программного обеспечения.
Значения этих параметров можно изменять, редактируя конфигурационные фай-
лы. Чтобы настроить параметр MaxClients в Apache, следует внести изменения в
файл httpd.conf. Настройка параметра max_connections в MySQL осуществляется
путем редактирования файла my. conf.
Выбор базы данных
Работая с MySQL из командной строки, необходимо указывать используемую
базу данных:
use books;
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
289
То же самое необходимо и при подключении из Web. База данных, которая долж-
на использоваться, указывается в параметре конструктора mysqli или функции
mysqli_connect (). Если впоследствии понадобится изменить используемую базу дан-
ных, для этого служит функция mysqli_select_db (), доступ к которой осуществляет-
ся следующим образом:
$db->select_db(dbname)
или
mysqli_select_db(db_resource, db_name)
И здесь поддерживаются обе версии функции — объектно-ориентированная и
процедурная, — причем процедурная отличается лишь префиксом mysql i_ в имени и
параметром, в котором должен передаваться ресурс подключения.
Выполнение запроса к базе данных
Чтобы осуществить запрос, необходимо воспользоваться функцией mysql i_query ().
Однако прежде этот запрос необходимо построить:
$query = "select * from books where ".$searchtype." like .$searchterm."%'";
В этом случае будет выполняться поиск значения, введенного пользователем
($searchterm) в указанном им поле ($searchtype). Вы, вероятно, обратили внимание
на то, что для установки соответствия мы употребили like, а не equal — при поиске в
базе данных обычно имеет смысл несколько расширить границы поиска.
Совет
Важно помнить, что запрос, отправляемый базе данных MySQL, не требует присутствия в кон-
це точки с запятой, в отличие от запроса, который вводится в среде монитора MySQL.
Теперь можно выполнить запрос:
$result = $db->query ($query);
Либо, если вы предпочитаете процедурный интерфейс, запрос будет выглядеть так:
$reBult = mysqli_query($db, $query);
Вы передаете запрос, который необходимо выполнить, и, в случае процедурного
интерфейса, связь с базой данных (в этом примере $db).
Объектно-ориентированная версия возвращает объект результата, а процедурная
версия — ресурс результата. (Это подобно работе функции подключения к базе дан-
ных.) В любом случае возвращаемое значение сохраняется в переменной ($result) для
дальнейшего использования. Функция возвращает значение false в случае ошибки.
Получение результатов запроса
Существует множество функций, которые позволяют различными способами вы-
членять нужные фрагменты из объекта или идентификатора результата. Объект или
идентификатор результата — это ключ доступа к возвращенным запросом строкам.
290
Часть II. Использование MySQL
В нашем примере мы подсчитали количество возвращенных запросом строк и
воспользовались функцией mysqli_f etch__assoc ().
В случае объектно-ориентированного подхода количество возвращенных
строк хранится в элементе num_rows объекта результата; обратиться к упомянуто-
му элементу можно следующим образом:
$num_results = $result->num„rows;
При процедурном подходе для получения количества возвращенных срок ис-
пользуется функция mysqli_num_rows (), которой необходимо передать идентифи-
катор результата:
$num_results = mysqli_num_rows($resulti;
Эта информация весьма полезна — если планируется обрабатывать или отобра-
жать результаты, знание количества строк позволяет организовать цикл по ним:
for ($i = 0; $i < nuir._resuJ ts; $! + + '
{
// обработка результатов
}
В каждой итерации этого цикла происходит вызов $result->f etch_assoc () (или
mysqli_fetch_assoc ()). Цикл не будет выполняться при отсутствии возвращенных
строк. Именно эта функция извлекает каждую строк}' из результирующего набора и
возвращает ее в виде массива, в котором каждый ключ является именем атрибута, а
каждое значение — соответствующим значением:
$row = $result->fetch_assoc();
Либо с использованием процедурного подхода:
$row = mysqli_fetch_assoc($result);
Имея массив $row, можно пройти по всем полям и должным образом отобразить
каждое из них:
echo '<br />ISBN:
echo stripslashes($row['isbn']);
Как упоминалось ранее, stripslashes () вызывается для того, чтобы “подчистить”
значение, прежде чем отображать его пользователю.
Существует несколько вариантов получения результата из идентификатора ре-
зультата. Вместо массива с именованными ключами можно воспользоваться нумеро-
ванным массивом, применив mysql i_f etch_row ():
$row = $result->fetch_row( ;
или, в случае процедурного подхода:
$row = mysqli_fetch_row<$result);
Значения атрибутов будут храниться в каждом из значений $row[0j, $row[l] и так
далее. (Функция mysqli_f etch_array () позволяет получить строку в виде массива
обеих типов.)
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
291
С помощью функции mysqli_fetch_object() можно также выбрать строку для
помещения внутрь объекта:
$row = $result->fetch_object();
или так:
$row = mysqli_fetch_object($result);
После этого доступ к каждому атрибуту можно получить с помощью
$row->title, $row->author и так далее.
Отсоединение от базы данных
Освобождение результирующего набора выполняется следующим образом:
$result->free();
или
mysqli_free_result($result);
Затем можно закрыть соединение с базой данных:
$db->close() ;
или
mysqli_close($db);
В явном отсоединении нет особой необходимости, поскольку по завершении вы-
полнения сценария соединение будет закрыто автоматически.
Внесение новой информации в базу данных
Вставка новых элементов в базу данных поразительно похожа на извлечение эле-
ментов из базы данных. Необходимо выполнить те же действия — установить соеди-
нение, отправить запрос и проверить результаты. Только в данном случае вместо
оператора SELECT будет использоваться INSERT.
Хоть все вроде и просто, взглянуть на пример не помешает. На рис. 11.3 показана
обычная HTML-форма для помещения новых книг в базу данных.
HTML-код этой страницы приведен в листинге 11.3.
292
Часть II. Использование MySQL
Рис. 11.3. Интерфейс для помещения новых книг в базу данных,
который может использоваться персоналом магазина “Буквофил”
Листинг 11.3. newbook. html — HTML-код страницы ввода информации о новых книгах
<html>
<head>
<С1б1е>Магазин "Буквофил" - Форма ввода новой книги</б1С1е>
</head>
<body>
<Ы>Магазин "Буквофил" — Форма ввода новой книги</Ы>
<form action="insert_book.php" method="post">
<table border="0">
<tr>
<td>ISBN</td>
ctdxinput type="text" name="isbn" maxlength="13" size="13"x/td>
</tr>
<tr>
<td>ABTOp</td>
< td> <input type="text" name="author" maxlength="30" size="30"x/td>
</tr>
<tr>
<td>HasBaHne</td>
< td> <input type="text" name="title" maxlength="60" size="30"></td>
</tr>
<tr>
<td>IIeHa, $</td>
< tdxinput type="text" name="price" maxlength="7" size="7"x/td>
</tr>
<tr>
< td colspan=" 2 "xinput type="submit" value=“ Зарегистрировать "x/td>
</tr>
</table>
</form>
</body>
</html>
Г лава 11. Доступ к базе данных MySQL из Web с помощью РНР
293
Результаты заполнения этой формы передаются в insert_book, php, а сценарий,
занимающийся деталями, выполняет несколько несложных проверок и пытается за-
писать данные в базу данных. Код этого сценария представлен в листинге 11.4.
Листинг 11.4. insertJbook .php — этот сценарий записывает новые книги в базу данных
<html>
<head>
<title>Mara3KH "Буквофил" — Результаты ввода новой книги</Д1Д1е>
</head>
<body>
<М>Магазин "Буквофил" — Результаты ввода новой книги</Ы>
<?php
// создание коротких имен переменных
$isbn=$_POST['isbn' ];
$author=$_POST['author'];
$title=$_POST['title'];
$price=$_POST['price'];
if (!$isbn || !$author || !$title || !$price)
{
echo 'Вы ввели не все необходимые сведения.<br />'
.'Пожалуйста, вернитесь на предыдущую страницу и повторите ввод.';
exit;
}
if (!get_magic_quotes_gpc())
{
$isbn = addslashes($isbn);
$author = addslashes($author);
$title = addslashes($title);
$price = doubleval($price);
}
@ $db = new mysqli('localhost', 'bookorama', 'bookoramal23', ’books');
if (mysqli_connect_errno())
{
echo 'Ошибка: He удалось установить соединение с базой данных.
Пожалуйста, повторите попытку позже.';
exit;
}
Squery = "insert into books values
('".$isbn."', '".$author."', '".$title."', '".$price."')";
$result = $db->query($query);
if ($result)
echo $db->affected_rows." книг(а, и) добавлено в базу данных.";
</body>
</html>
Результаты успешного добавления книги в базу данных можно видеть на рис. 11.4.
294
Часть II. Использование MySQL
Рис. 11.4. Сценарий завершен успешно и сообщает о добавле-
нии книги в базу данных
После изучения кода insert_book. php становится ясно, что он во многом похож
на код сценария для извлечения информации из базы данных. Мы проверяем, что все
поля формы заполнены, и. если необходимо, с помощью функции addslashes () со-
ответствующим образом их форматируем для вставки в базу данных:
if (!get_magic_quotes_gpc())
{
$isbn = addslashes<$isbn);
$author = addslashes($author);
$title = addslashes($title) ;
$price = doubleval i $price) ,-
)
Поскольку цены хранятся в базе в виде чисел с плавающей точкой, им не нужны
косые черты. Этого же можно добиться с помощью функции doubleval (), которая
отфильтровывает все неподходящие символы в числовом поле. Эта функция рас-
сматривалась в главе 1. Эта же функция позаботится и обо всех символах валюты,
которые пользователь может ввести в форму.
Мы снова соединяемся с базой данных, создавая экземпляр mysqli и подготавли-
вая запрос, который должен быть отправлен базе данных. В данном случае это SQL-
запрос INSERT.
$query = "insert into books values
(1’.Sisbn."', ’".Sauthor."', '".$title."' '".$price."')";
$result = $db->query($query),
Этот запрос выполняется в базе данных как обычно — с помощью вызова
$db->query () (или mysql i_query (), если вы предпочитаете процедурный подход).
Одно существенное различие между' SQL-операторами INSERT и SELECT связано с
использованием функции mysqli_affected_rows(). В процедурной версии это дей-
ствительно функция, тогда как в объектно-ориентированной версии она представля-
ет собой переменную-член класса:
echo $db->affected_rows." книг(а, и) добавлено в базу данных.";
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
295
В предыдущем сценарии функция mysql i_num_rows () применялась для определе-
ния количества строк, возвращаемых запросом SELECT. При написании запросов,
которые изменяют базу данных, например, INSERT, DELETE, UPDATE, вместо этой функ-
ции следует использовать mysqli_affected_rows ().
Мы ознакомились с основами использования баз данных MySQL из РНР.
Использование подготовленных
операторов
Библиотека mysqli поддерживает использование подготовленных операторов.
Они полезны для ускорения выполнения множества одних и тех же запросов, но с
разными данными. Подготовленные операторы также предохраняют от атак внедре-
ния SQL-кода во вводимые данные.
Основная концепция подготовленного оператора состоит в раздельной отправке
MySQL шаблона запроса, который должен быть выполнен, и данных для заполнения
этого шаблона. Вы можете посылать множество одних и тех же данных одному подго-
товленном}' оператору; эта возможность очень полезна при выполнении групповых
вставок.
В сценарии insert_book. php подготовленные операторы можно использовать
следующим образом:
$query = 11 insert into books values (?, ?, ?, ?) ";
$stmt = $db->prepare($query);
$stmt->bind_param("sssd”, $isbn, $author, $title, Sprice);
$stmt->execute();
echo $stmt->affected_rows.’ книг(а, и) добавлено в базу данных.’;
$stmt->close();
Рассмотрим приведенный код, строка за строкой.
При построении запроса вместо каждого фрагмента данных помещается знак во-
проса (?). При этом не используются ни кавычки, ни какие-либо друтие разделитель-
ные символы.
Во второй строке вызывается $db->prepare (), или mysqli_stmt_prepare () в
процедурной версии. Эта строка создает объект оператора или ресурс, который за-
тем будет использоваться для дальнейшей обработки.
В объекте оператора определен метод bind_param(). (В процедурной версии это
функция mysqli_stmt_bind_param ().) Назначение bind_param() состоит в том, что-
бы сообщить РНР, какими переменными должны быть замещены знаки вопроса в
запросе. Первый параметр представляет собой строку формата, чем-то похожую на
строку формата, используемую в функции printf (). Используемое значение, "sssd",
означает, что будут передаваться три строки и число двойной точности. Допускается
также передача целочисленного значения (i) и большого двоичного объекта (Ь). По-
сле параметра формата должны следовать переменные, значения которых будут за-
мещать знаки вопроса в запросе, в порядке их следования.
Вызов $stmt->execute () (mysqli_stmt_execute () в процедурной версии) выпол-
няет запрос. Затем можно посмотреть количество задействованных строк и закрыть
оператор.
296
Часть II. Использование MySQL
Чем же так полезен подготовленный оператор? Замечательно то, что можно из-
менить значения четырех связываемых переменных и еще раз выполнить запрос без
необходимости его подготовки. Данная возможность исключительно полезна при
выполнении крупных групповых вставок.
Подобно связыванию параметров, можно связывать и результаты. Для запросов
SELECT можно использовать $stmt->bind_result() (или mysqli_stmt._bind_result. ())
для построения списка переменных, в которые должны помещаться столбцы резуль-
тата. Каждый раз, когда вы вызываете $stmt->fetch() (или mysqli_stmt_fetch()),
значения столбцов из следующей строки результирующего набора заносятся в задан-
ные переменные связи. Например, в рассмотренном выше сценарии поиска книг,
можно было бы воспользоваться следующим оператором:
$st.mt->bind_result($isbn, Sauthor, $title, $price) ;
для связывания перечисленных четырех переменных с четырьмя столбцами, возвра-
щаемыми запросом. После вызова
$stmt.->execut.e() ;
можно делать следующий вызов:
$stmt->fetch();
в цикле. Каждое такое обращение извлекает следующую строку из результата и зано-
сит ее в переменные.
В одном и том же сценарии можно использовать и mysqli_stmt._bind_param (), и
mysqli_stmt_bind_result().
Следует отметить, что на время написания этой книги (PHP5RC2) подготовлен-
ные операторы вызывали аварийное завершение Apache под Windows, однако нор-
мально работали под Unix.
Использование других РНР-интерфейсов
работы с базами данных
РНР поддерживает библиотеки для подключения к огромному количеству баз дан-
ных, включая Oracle, Microsoft SQLServer и PostgreSQL.
В целом принципы подключения и запроса к любой из этих баз данных во многом
совпадают. Имена функций могут различаться, а различные базы данных могут предос-
тавлять разные функциональные возможности, но если вы можете подключиться к
MySQL, эти знания легко применить и в отношении любой другой базы данных.
Если необходимо использовать базу данных, которая не имеет специфической
библиотеки, доступной в РНР, можно прибегнуть к обобщенным функциям ODBC.
ODBC (Open Database Connectivity) — это открытый интерфейс доступа к базам дан-
ных, который является стандартом подключения к базам данных. Функциональные
возможности ODBC достаточно ограничены, однако на то имеются вполне очевид-
ные причины: универсальная совместимость не сочетается с использованием специ-
фических возможностей какой-то конкретной системы.
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
297
В дополнение к библиотекам, поставляемым с РНР, доступны также такие классы
абстракции баз данных, как PEAR: : DB, позволяющие использовать одни и те же имена
функций для различных типов баз данных.
Использование обобщенного интерфейса
базы данных: PEAR DB
Рассмотрим краткий пример использования уровня абстракции PEAR DB. Это
один из основных, и вероятно, наиболее широко используемых компонентов PEAR.
Если PEAR установлен, компонент DB также должен быть установлен. Если это не
так, обратитесь к разделу “Установка PEAR” приложения А.
Для сравнения взгляните, как можно было бы записать сценарий получения ре-
зультатов поиска с использованием DB.
Листинг 11.5. results_generic .php — этот сценарий получает результаты из базы
данных MySQL и форматирует их для отображения
<html>
<head>
<title>Mara3MH "Буквофил" - Результаты поиска*:/title>
</head>
<body>
<111>Магазин "Буквофил" - Результаты поиска</Ы>
<?php
// создание коротких имен переменных
$searchtype=$_POST('searchtype'i;
$searchcerm=$_POST['searchterm'j;
$searchterm = trim($searchterm);
if (!Ssearchtype || !$searchterm)
(
echo 1 Вы не ввели параметры поиска.
Пожалуйста, вернитесь на предыдущую страницу и повторите ввод.';
exit;
}
if (!get_magic_quotes_gpc())
(
$searchtype = addslashes($searchtype);
$searchterm = addslashes($searchterm);
}
// настройка для использования PEAR DB
require_once('DB.php1);
$user = 'bookorama';
$pass = 'bookoramal23' ;
$host = 'localhost';
$db_name = 'books';
// настройка строки универсального соединения или DSN
$dsn = "mysqli://$user:$pass@Shost/$db_name”;
// соединение с базой данных
298
Часть II. Использование MySQL
$db = DB::connect($dsn);
// проверка работоспособности соединения
if (DB::isError($db))
(
echo $db->getMessage();
exit ;
}
// выполнение запроса
$query = "select * from books where ".$searchtype.” like
'%".$searchterm
$result = $db->query($query);
// проверка достоверности результатов запроса
if (DB::isError($result))
{
echo $db->getMessage();
exit;
}
// получение количества возвращенных строк
$num_results = $result->numRows();
// отображение каждой возвращенной строки
for ($i=0; $i < $num_results; $i+ + )
(
$row = $result->fetchRow(DB_FETCHMODE_ASSOC);
echo ’ <pxstrong> ' . ($i+l) . ' . Название: ';
echo htmlspecialchars(stripslashes($row['title']));
echo '</strongxbr />Автор: ';
echo stripslashes($row('author' ]);
echo '<br />ISBN: ';
echo stripslashes($row['isbn']);
echo '<br />Цена: ';
echo stripslashes(Srow['price']);
echo '</p>';
1
// отсоединение от базы данных
$db->disconnect();
</body>
</html>
Посмотрим, чем этот сценарий отличается от использованного ранее.
Для подключения к базе данных служит строка:
$db = DB::connect($dsn);
Эта функция принимает универсальную строку соединения, содержащую все па-
раметры, необходимые для подключения к базе данных. В этом легко убедиться,
взглянув на формат строки соединения:
$dsn = "mysqli://$user:$pass@$host/$db_name";
Глава 11. Доступ к базе данных MySQL из Web с помощью РНР
299
Затем с помощью метода isError () мы проверяем наличие ошибки при установке
соединения, и, если ошибка произошла, выводим сообщение об ошибке и осуществ-
ляем выход:
if (DB::isError($db))
{
echo $db->getMessage();
exit;
}
Далее, при условии, что все было выполнено правильно, мы настраиваем запрос и
выполняем его:
$result = $db->query(Squery);
Мы можем проверить количество возвращенных строк:
$num_results = $result->numRows();
Извлечение каждой строки выполняется следующим образом:
$row = $result->fetchRow(DB_FETCHMODE_ASSOC);
Обобщенный метод fetchRowO позволяет осуществлять выбор строки во множе-
стве различных форматов — параметр DB_FETCHMODE_ASSOC показывает, что строку'
желательно вернуть в виде ассоциативного массива.
После вывода возвращенных строк сценарий завершается закрытием соединения
с базой данных:
$db-disconnect () ;
Как видите, этот обобщенный пример во многом похож на первый сценарий.
Преимущество использования DB состоит в том, что нужно запомнить только
один набор функций работы с базой данных, и, если придется изменить программное
обеспечение базы данных, код потребует лишь минимальных изменений.
Поскольку эта книга посвящена MySQL, для увеличения скорости работы и гибко-
сти мы будем пользоваться встроенными библиотеками MySQL. Тем не менее, в ре-
альных проектах может потребоваться использовать пакет DB, поскольку в ряде слу-
чаев подобный уровень абстракции оказывается буквально незаменимым.
Дополнительные источники информации
Дополнительно о совместном использовании MySQL и РНР можно прочитать в
соответствующих разделах руководств по РНР и MySQL.
Информация об ODBC доступна по адресу:
http://www.webopedia.com/TERM/O/ODBC.html
Что дальше
В следующей главе мы подробнее рассмотрим вопросы администрирования
MySQL и обсудим способы оптимизации работы базы данных.
300
Часть II. Использование MySQL
12
Дополнительные сведения
по администрированию
MySQL
В этой главе освещены некоторые более сложные темы, связанные с использова-
нием MySQL, в том числе расширенные полномочия, безопасность и оптими-
зация. В главе, помимо прочих, рассматриваются следующие темы:
Подробное ознакомление с системой полномочий.
Обеспечение безопасности базы данных MySQL.
Получение дополнительной информации о базах данных.
Ускорение выполнения запросов за счет использования индексов.
Оптимизация базы данных.
Резервное копирование и восстановление.
Реализация репликации.
Подробное ознакомление с системой
полномочий
В главе 9 был описан процесс определения пользователей и предоставления им
полномочий. Для предоставления полномочий служит команда GRANT. Если вы соби-
раетесь выполнять действия по администрированию базы данных MySQL, полезно
знать, что именно выполняет эта команда и как из нее извлечь максимальную пользу.
Выполнение оператора GRANT оказывает влияние на таблицы в специальной базе
данных с именем mysql. Информация о полномочиях хранится в пяти таблицах этой
базы данных. Учитывая этот факт, при выдаче полномочий для работы с базами дан-
ных следует с осторожностью предоставлять доступ к базе данных mysql.
По ходу дела заметим, что команда GRANT стала доступна, начиная с версии MySQL
3.22.11.
Просмотреть содержимое базы данных mysql можно, входя в систему в качестве
администратора и набрав следующую команду:
use mysql;
После этого таблицы в этой базе данных можно просматривать как обычно, с по-
мощью команды:
show tables;
Результат выполнения команды будет выглядеть подобно показанном^' ниже:
| Tables_in_mysql j
columns_priv |
db |
func I
help_category |
help_keyword |
help_relation |
help_topic |
host j
proc |
tables_priv I
user I
Каждая из таблиц этой базы данных содержит системную информацию. Пять из
них — user, host, db, tables_priv и columns__priv - хранят информацию о полномо-
чиях. Иногда их называют таблицами полномочий. Эти таблицы различаются своими
специфическими функциями, но все они служат одной общей цели определения того,
какие действия разрешено выполнять пользователям, а какие запрещено. Каждая из
них содержит два типа полей: поля области действия, определяющие пользователя,
хост и часть базы данных, к которым применимы данные полномочия, и поля полно-
мочий, которые определяют действия, разрешенные данном}' пользователю в данной
области действия.
Таблицы user и host служат для определения того, может ли пользователь вообще
подключаться к серверу MySQL, а также того, обладает ли он полномочиями админи-
стратора. Таблицы db и host определяют базы данных, к которым пользователь мо-
жет получать доступ. Таблица tables__priv определяет доступные для пользователя
таблицы внутри базы данных, а таблица columns_priv — доступные столбцы внутри
таблиц.
Таблица user
Таблица user содержит сведения о глобальных полномочиях пользователей. Она
определяет, может ли пользователь вообще подключаться к серверу the MySQL, и то,
предоставлены ли ему какие-либо полномочия глобального уровня — то есть полно-
мочия, применимые к каждой базе данных в системе.
Структуру этой таблицы можно просмотреть с помощью оператора describe
user;. Схема таблицы user представлена в табл. 12.1.
Каждая строка в этой таблице соответствует набору полномочий, предоставлен-
ных пользователю User, входящему в систему с хоста Host с паролем Password. В дан-
ной таблице эти поля представляют собой поля обмети действия, поскольку они опи-
сывают область действия других полей, называющихся полями полномочий.
302
Часть II. Использование MySQL
Таблица 12.1. Схема таблицы user базы данных mysql
Поле Tun
Host varchar(60)
User varchar(16)
Password varchar(41)
Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_pr iv Repl_client_priv ssl_type ssl_cipher x509_issuer enum('N','Y1) enum('N','Y') enum('N','Y') enum('N','Y') enum('N','Y') enum('N','Y') enum('N','Y') enum ( ' N' , ' Y' ) enum ( ' N' , ' Y') enum('N','Y') enum('N','Y') enum('N','Y') enum('N','Y') enum (’ N' , ' Y') enum('N','Y1) enum ( ' N' , ' Y') enum('N','Y') enum('N','Y’) enum('N','Y’) enum('N','Y') enum('N','Y') enum ( ' N' , ' Y') enum('N','Y') enum('N','Y')
x509_subject max_questions max_updates max_connections enum('N',' Y') e num('N', ' Y') enum (' N' , ' Y' ) enum('N', 'Y')
Полномочия, перечисленные в этой таблице (и последующих), соответствуют
полномочиям, выдаваемым с помощью команды GRANT, которая описана в главе 9.
Например, Select_priv соответствует полномочиям на выполнение команды SELECT.
Если пользователь располагает конкретными полномочиями, значение в соответ-
ствующем столбце будет равно Y. И наоборот, если данные полномочия не предос-
тавлены, значение будет равно N.
Все полномочия, перечисленные в таблице user — глобальные, то есть они при-
меняются ко всем базам данных в системе (включая базу данных mysql). Следовательно,
для администраторов некоторые значения будут установлены равными Y, но для
Глава 12. Дополнительные сведения по администрированию MySQL
303
большинства пользователей все значения должны быть равными N. Обычные пользо-
ватели должны располагать правами доступа к соответствующим базам данных, а не
ко всем таблицам.
Таблицы <ЗЬ и host
Информация о большинстве полномочий рядовых пользователей хранится в таб-
лицах db и host.
Таблица db определяет, к каким базам данных могут получать доступ те или иные
пользователи из тех или иных хостов. Перечисленные в этой таблице полномочия
применимы к любой базе данных, указанной в конкретной строке.
Таблица host, дополняет таблицы user и db. Если пользователь может подклю-
чаться к базе данных нескольких хостов, для этого пользователя в таблице user или
db какой-либо хост не указывается. Вместо этого пользователь будет иметь набор за-
писей в таблице host, каждая из которых будет определять полномочия для каждой
комбинации пользователь-хост.
Схемы этих двух таблиц приведены в табл. 12.2 и табл. 12.3.
Таблица 12.2. Схема таблицы db базы данных mysql
Поле Tun
Host char(60)
Db char(64)
User char(16)
Select_priv enum('N','Y')
Insert_priv enum('N','Y')
Update_priv enum('N','Y')
Delete_priv enum('N','Y')
Create_priv enum('N','Y')
Drop_priv enum('N','Y')
Grant_priv enum('N','Y')
References_priv enum('N','Y')
Index_priv enum('N','Y')
Alter_priv enum('N','Y')
Create_tmp_tables_priv enum('N','Y')
Lock_tables_priv enum('N','Y')
Таблица 12.3. Схема таблицы host базы данных mysql
Поле Tun
Host char(60)
Db char(64)
Select_priv enum('N','Y')
Insert_priv enum('N','Y')
Update_priv enum('N','Y')
Delete_priv enum('N','Y')
304
Часть II. Использование MySQL
Окончание табл. 12.3
Поле Tun
Create_priv enum( ' N' , ' Y' )
Drop__priv enum( ' N' ,'Y')
Grant_priv enum('N','Y')
References_priv enum( ' N' ,'Y')
Index__priv enum(’N', 1Y1)
Alter__priv enum ('N1,'Y1)
Greate_tmp_tables_priv enum('N','Y')
Lock_tables_priv enum(’N’,'Y')
Таблицы tables_priv и columns_priv
Таблицы tables_priv и columns_priv используются для хранения полномочий
уровней, соответственно, таблицы и столбца. Они работают подобно таблице db за
исключением того, что предоставляют информацию о полномочиях по отношению к
таблицам внутри конкретной базы данных и по отношению к столбцам внутри кон-
кретной таблицы.
Структура этих таблиц несколько отличается от структуры таблиц user, db и host.
Схемы таблиц tables_priv и columns_priv показаны в табл. 12.4 и табл. 12.5.
Таблица 12.4. Схема таблицы tables_priv базы данных mysQl
Поле Tun
Host char(60)
Db char(64)
User char(16)
Table_name char(60)
Grantor char(77)
Timestamp timestamp(14)
Table_priv set('Select', 'Insert', 'Update', 'Delete', 'Create',
'Drop', 'Grant', 'References', 'Index', 'Alter')
Column__priv set ('Select', 'Insert', 'Update', ’References')
Таблица 12.5 Схема таблицы columns_priv базы данных inysql
Поле Tun
Host char(60)
Db char(64)
User char(16)
Table_name char(64)
Column_naine char(64)
Timestamp timestamp(14)
Column_priv set('Select', 'Insert', 'Update', 'References')
Глава 12. Дополнительные сведения по администрированию MySQL
305
Столбец Grantor таблицы tables_priv хранит имя пользователя, который пре-
доставил полномочия данному пользователю. В столбце Timestamp обеих этих таблиц
содержится значение даты и времени выдачи полномочий.
Управление доступом: использование таблиц
полномочий в среде MySQL
Используя таблицы полномочий, MySQL определяет действия, которые разреше-
но выполнять пользователю, в ходе двухэтапного процесса:
1. Проверка права на подключение. На этом этапе на основе информации, по-
лученной из таблицы user, MySQL проверяет, имеет ли пользователь право на
подключение. Эта аутентификация выполняется на основе имени пользовате-
ля. имени хоста и пароля. Если поле имени пользователя пустое, оно соответ-
ствует всем пользователям. Имена хостов можно указывать с использованием
группового символа (%). Этот символ может использоваться в качестве полного
значения поля (то есть символ % соответствует всем хостам) или в качестве
части имени хоста (например, %. tangledweb.com.au соответствует всем хос-
там, имена которых заканчиваются строкой .tangledweb.com.au). Если поле
пароля пустое, пароль для подключения не требуется. Однако система будет
защищена в большей степени, если избегать применения пустых полей для
имен пользователей, групповых символов в именах хостов и пользователей без
паролей. Если поле имени хоста пустое, для поиска соответствующих записей
user и host MySQL обращается к таблице host.
2. Проверка права на выполнение запросов. После того как соединение с сер-
вером установлено, при каждом вводе запроса MySQL проверяет наличие соот-
ветствующего уровня полномочий для выполнения этого запроса. Система на-
чинает проверку с глобальных полномочий (в таблице user) и, если этих
полномочий недостаточно, проверяет таблицы db и host. Если и этих полно-
мочий недостаточно, MySQL проверит таблицу tables_priv и, наконец, если и
этого окажется мало — таблицу columns—priv.
Обновление полномочий: когда изменения
вступают в силу?
Сервер MySQL автоматически считывает таблицы предоставления полномочий во
время своего запуска и при выполнении операторов GRANT и REVOKE. Однако, зная,
где и как хранятся эти полномочия, их можно изменять вручную. При обновлении
полномочий вручную сервер MySQL не заметит их изменения.
Об изменениях серверу потребуется сообщить, и это можно выполнить тремя
способами. Можно ввести команду
flush privileges;
в командной строке MySQL (чтобы эту команду можно было использовать, необходи-
мо войти в систему в качестве администратора). Этот способ обновления полномо-
чий используется наиболее часто.
306
Часть II. Использование MySQL
Можно также выполнить любую из команд
mysqladmin flush-privileges
или
mysqladmin reload
непосредственно в операционной системе.
После выполнения этих действий полномочия глобального уровня будут провере-
ны при следующем подключении пользователя к серверу, полномочия уровня базы
данных будут проверены при следующем выполнении оператора use, а полномочия
уровней таблицы и столбца — при следующем запросе со стороны пользователя.
Обеспечение безопасности базы
данных MySQL
Безопасность очень важна, особенно при подключении базы данных MySQL к
Web-сайту. В следующих разделах описаны меры предосторожности, которые нужно
предпринимать для защиты базы данных.
MySQL с точки зрения операционной системы
При использовании Сnix-подобной операционной системы запуск сервера MySQL
(mysqld) в качестве привилегированного процесса — не самая лучшая идея. В этом
случае пользователь MySQL получает полный набор полномочий для выполнения
чтения и записи файлов в любом каталоге операционной системы. Это исключитель-
но важный момент, который легко упустить из виду, чем и воспользовались для зна-
менитого взлома Web-сайта Apache. (К счастью, они оказались “белыми и пушисты-
ми”, и их единственной целью было укрепление системы безопасности.)
Имеет смысл настроить пользователя MySQL специально для запуска mysqld.
Кроме того, доступ к каталогам (в которых хранятся физические данные) можно раз-
решить только пользователю MySQL. Во многих установках сервер настраивают так,
чтобы он запускался с идентификатором пользователя mysql, входящего в состав
группы mysql.
В идеале следует также помещать сервер MySQL позади брандмауэра. Тем самым
можно предотвратить подключение с компьютеров, не обладающих соответствую-
щими полномочиями. Следует также проверить, возможно ли внешнее подключение
к серверу через порт 3306. Через этот порт сервер MySQL действует по умолчанию, и
он должен быть закрыт брандмауэром.
Пароли
Убедитесь, что все пользователи (особенно root!) имеют пароли, и что эти пароли
правильно выбраны и регулярно обновляются, подобно паролям операционной сис-
темы. При этом важно помнить, что использование паролей, которые представляют
собой или содержат слова из какого-либо словаря — идея весьма неудачная. Лучше
всего применять комбинации букв и цифр.
Если планируете хранить пароли в файлах сценариев, убедитесь, что каждый из таких
сценариев доступен только тому пользователю, чей пароль хранится в данном файле.
Глава 12. Дополнительные сведения по администрированию MySQL
307
PHP-сценариям, служащим для подключения к базе данных, требуется доступ к
паролю конкретного пользователя. Это можно сделать достаточно безопасно, по-
мещая имя учетной записи и пароль пользователя в файл, названный, например,
dbconnect .php, который затем можно включить в программу при необходимости.
Этот сценарий можно надежно хранить вне дерева Web-документов, предоставляя
доступ к нему только соответствующему пользователю.
Помните, что при помещении этих сведений в файл с расширением . inc или ка-
ким-либо другим расширением в дереве Web-документов, следует тщательно прове-
рить, что Web-сервер будет интерпретировать их как PHP-сценарии, не разрешая
просмотр этих сведений в Web-браузере.
Пароли не следует хранить в виде обычного текста в базе данных. Пароли MySQL
не хранятся в таком виде, но в Web-приложениях часто требуется хранить также
имена учетных записей и пароли членов Web-сайта. Шифрование паролей (однона-
правленное) можно выполнить с помощью MySQL-функции SHA1 (). Помните, что в
случае вставки пароля, представленного в этом формате, с помощью оператора
SELECT (например, для регистрации пользователя), эту же функцию придется вызы-
вать еще раз для проверки пароля, введенного пользователем.
Такой подход будет применяться при реализации проектов в части V книги.
Полномочия пользователя
Знание — сила. Поэтому необходимо хорошо понимать особенности работы сис-
темы полномочий MySQL и последствия выдачи конкретных полномочий. Ни одному
пользователю не следует предоставлять больше полномочий, чем ему требуется. Эти
полномочия необходимо проверять, просматривая таблицы полномочий.
В частности, не выдавайте полномочия PROCESS, FILE, SHUTDOWN и RELOAD ни од-
ному пользователю кроме администратора, если только это не абсолютно необходи-
мо. Полномочия PROCESS могут быть использованы для слежения за тем, что делают и
вводят другие пользователи, в том числе за вводом паролей. Полномочия FILE позво-
ляют считывать и записывать файлы операционной системы (в числе которых, на-
пример, файл /etc/password в системе Unix).
Полномочия GRANT также должны предоставляться с осторожностью, поскольку
они разрешают пользователям делиться своими полномочиями с другими.
При определении пользователей предоставляйте им доступ только из тех хостов,
с которых они будут подключаться к базе данных. Если у вас есть пользователь
jane@localhost — это прекрасно, но просто jane — достаточно распространенное
имя, и пользователь с таким именем может входить в систему откуда угодно — при
этом проверить аутентичность jane не представляется возможным. По аналогичным
причинам следует избегать применения групповых символов в именах хостов.
Безопасность можно еще больше увеличить, если указывать в таблице host IP-
адреса, а не имена доменов. Это позволяет избежать проблем, связанных с ошибками
ввода или атаками на сервер DNS. Данный подход можно реализовать, запустив де-
мон MySQL с параметром —skip-name-resolve, который означает, что все значения
столбца хоста должны быть либо IP-адресами, либо именем локального узла.
Следует также запретить пользователям, которые не являются администраторами,
доступ к программе mysqladmin на Web-сервере. Поскольку эта программа запускает-
308
Часть II. Использование MySQL
ся из командной строки, наличие доступа к ней по существу означает наличие полно-
мочий доступа к операционной системе.
Проблемы, связанные с Web
Подключение базы данных MySQL к Web порождает нескольку специфических
проблем безопасности.
Неплохо начать с создания пользователя специально для подключения к Web. При
этом ему можно выдать минимально необходимые полномочия и не предоставлять
такие полномочия как. например, DROP, ALTER или CREATE. Этом}’ пользователю мож-
но было бы выдать полномочия SELECT только для таблиц catalog и полномочия IN-
SERT — только для таблиц order. Приведенный пример — еще одна иллюстрация при-
менения принципа минимальных полномочий.
Предостережение
В предыдущей главе было описано использование PHP-функций addslashes () и stripslashes ()
для удаления из строки сомнительных символов. Перед отправкой любых данных в MySQL
важно не забывать о необходимости реализации как этих действий, так и общей "чистки” дан-
ных. Следует не забывать об использовании функции doubleval () для проверки того, что чи-
словые данные таковыми являются в действительности. Пропуск этой проверки — часто
встречающаяся ошибка: люди помнят о необходимости вызова функции addslashes (), но не
проверяют числовые данные.
Проверять все данные, поступающие от пользователя, необходимо всегда. Даже
если HTML-форма состоит только из полей выбора и переключателей, кто-либо мо-
жет изменить L'RL-адрес. чтобы попытаться взломать сценарий. Целесообразно так-
же проверять размер поступающих данных.
Если пользователи вводят пароли или конфиденциальные данные, которые долж-
ны храниться в базе данных, помните, что если только не использовать протокол
безопасных сокетов (Secure Sockets Layer — SSL), эти данные будут передаваться из
браузера серверу в виде обычного текста. Использование SSL более подробно рас-
сматривается в последующих главах.
Получение дополнительной информации
о базах данных
До сих пор мы использовали операторы SHOW и DESCRIBE для получения списков
таблиц в базе данных и столбцов в этих таблицах. В последующих разделах мы кратко
рассмотрим другие способы применения этих операторов, а также оператора
EXPLAIN для получения дополнительной информации о способе выполнения SELECT.
Получение информации с помощью оператора SHOW
Ранее мы использовали следующую конструкцию:
show tables;
для получения списка таблиц базы данных.
Глава 12. Дополнительные сведения по администрированию MySQL
309
Оператор
show databases;
отображает список доступных баз данных. Затем с помощью оператора SHOW TABLES
можно просмотреть список таблиц в одной из этих баз данных:
show tables from books;
При использовании оператора SHOW TABLES без указания базы данных, по умолча-
нию отобразится список таблиц используемой базы данных.
Если имена таблиц известны, можно получить список столбцов:
show columns from orders from books;
Если имя базы данных опустить, оператор SHOW COLUMNS выведет список исполь-
зуемой в текущий момент базы данных. Можно использовать также форму записи
таблица.столбец:
show columns from books.orders;
Еще одну полезную вариацию оператора SHOW можно применять для выяснения
полномочий, выданных пользователю. Например, оператор
show grants for bookorama;
приведет к следующему результату:
| Grants for bookorama@% |
+-----------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'bookorama'@'%' |
| IDENTIFIED BY PASSWORD '*lECE648641438A28E1910D0D7403C5EE9E8B0A85' |
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER j
| ON 'books'.* TO ’bookorama' @ % ' j
Приведенные в этом примере операторы GRANT не являются обязательными для
предоставления полномочий конкретному пользователю, а представляют собой ско-
рее эквивалентные обобщенные операторы, которые создают текущий уровень пол-
номочий пользователя.
Примечание
Оператор show grants появился в версии MySQL 3.23.4. В предшествующих версиях он не ра-
ботает.
Можно использовать также множество других вариаций оператора SHOW. Общий
список всех вариаций представлен в табл. 12.6.
Получение информации о столбцах с помощью
оператора DESCRIBE
Вместо оператора SHOW COLUMNS можно использовать оператор DESCRIBE, кото-
рый подобен оператору DESCRIBE в Oracle (еще одна СУРБД). Основной синтаксис
этого оператора выглядит следующим образом:
DESCRIBE table [column];
310
Часть II. Использование MySQL
Таблица 12.6. Синтаксис оператора SHOW
Вариация
Описание
SHOW DATABASES
[LIKE database]
SHOW [OPEN] TABLES
[FROM database]
[LIKE table]
SHOW COLUMNS FROM table
[FROM database]
[LIKE column]
SHOW INDEX FROM table
[FROM database]
SHOW STATUS
[LIKE status_item]
SHOW [GLOBAL|SESSION]
VARIABLES
[LIKE variable_name]
SHOW [FULL] PROCESSLIST
SHOW TABLE STATUS
[FROM database]
[LIKE database]
SHOW GRANTS FOR user
Выводит список доступных баз данных, с необязательным ото-
бражением их имен, которые подобны database.
Выводит список таблиц используемой в текущий момент либо
указанной базы данных database, с необязательным отображе-
нием имен таблиц, которые подобны tabl е.
Выводит список всех столбцов конкретной таблицы используе-
мой в текущий момент либо указанной базы данных, с необяза-
тельным отображением имен столбцов, которые подобны
column. Вместо оператора SHOW COLUMNS можно использовать
оператор SHOW FIELDS.
Выводит сведения обо всех индексах в конкретной таблице ис-
пользуемой в данный момент либо указанной базы данных
database. Вместо этого оператора можно использовать опера-
тор SHOW KEYS.
Предоставляет информацию о количестве системных элемен-
тов, такую как число выполняющихся потоков. Выражение LIKE
используется для сопоставления имен этих элементов; так, на-
пример, 'Thread%' соответствует элементам 'Threads_cached',
'Threads_connected' и Threads_running'.
Отображает имена и значения системных переменных MySQL
наподобие номера версии. Выражение LIKE можно использо-
вать для их сопоставления аналогично тому, как это делается в
операторе SHOW STATUS.
Отображает все выполняющиеся в системе процессы, то есть
выполняющиеся в данный момент запросы. Большинство поль-
зователей будут видеть информацию о своих собственных пото-
ках, но при наличии у них полномочий PROCESS они могут видеть
информацию о процессах любых пользователей — в том числе,
присутствующие в запросах пароли. По умолчанию запросы
должны усекаться до 100 символов. Применение необязательного
ключевого слова FULL ведет к отображению полных запросов.
Отображает информацию о каждой таблице в используемой либо
в указанной базе данных da tabase; допускается применение груп-
повых символов. Информация включает в себя сведения о типе
таблицы и времени последнего обновления каждой таблицы.
Отображает операторы GRANT, необходимые для предостав-
ления указанному пользователю user его текущего уровня пол-
номочий.
SHOW PRIVILEGES
SHOW CREATE DATABASE db
SHOW CREATE
TABLE tablename
Отображает различные поддерживаемые сервером полномочия.
Отображает оператор CREATE DATABASE, который создал бы
указанную базу данных.
Отображает оператор CREATE TABLE, который создал бы ука-
занную таблицу.
Глава 12. Дополнительные сведения по администрированию MySQL
311
Окончание табл. 12.6
Вариация Описание
SHOW ‘ [STORAGE] ENGINES Отображает механизмы хранения, доступные в данной системе, с указанием механизма, используемого по умолчанию. (Меха- низмы хранения рассматриваются в главе 13.)
SHOW INNODB STATUS Отображает сведения о текущем состоянии механизма хране- ния InnoDB.
SHOW [BDB] LOGS Выводит информацию о журнальных файлах механизма хране- ния BDB.
SHOW WARNINGS [LIMIT Отображает любые сообщения об ошибках, предупреждения
[offset,] row_count] или уведомления, сгенерированные последним выполненным оператором.
SHOW ERRORS [LIMIT Отображает только сообщения об ошибках, сгенерированные
[offset,] row_count] последним выполненным оператором.
Эта команда выводит информацию обо всех столбцах таблицы table или о кон-
кретном столбце, если указан параметр column. При желании в column можно исполь-
зовать групповые символы.
Получение информации о способе выполнения
запросов с помощью оператора EXPLAIN
Оператор EXPLAIN можно применять двумя способами. Во-первых, можно исполь-
зовать команду:
EXPLAIN table;
Результат выполнения этой команды аналогичен результату выполнения операто-
ра DESCRIBE table или SHOW COLUMNS FROM table.
Второй и более интересный способ применения оператора EXPLAIN позволяет
выяснить, как именно MySQL вычисляет запрос SELECT. Чтобы использовать EXPLAIN
именно так, достаточно поместить слово EXPLAIN перед оператором SELECT.
К услугам оператора EXPLAIN можно прибегнуть во время отладки сложного, явно
неправильного запроса, либо в тех случаях, когда обработка запроса занимает намно-
го больше времени, чем должна. При создании сложного запроса с помощью EXPLAIN
его можно заранее проверить без действительного выполнения запроса. Располагая
результатом выполнения этого оператора, при необходимости, можно изменить соз-
данный SQL-код с целью его оптимизации. Данный оператор служит также удобным
обучающим инструментом.
Например, попробуйте выполнить следующий запрос в базе данных магазина
“Букв оф ил”:
explain
select customers.name
from customers, orders, order_items, books
where customers.customerid = orders.customerid
and orders.orderid = order_items.orderid
and order_items.isbn = books.isbn
and books.title like '%Java%';
312
Часть II. Использование MySQL
Первый столбец, id, отображает идентификационный номер оператора SELECT
внутри запроса, на который ссылается данная строка.
Столбец select_type содержит информацию о типе использованного запроса.
Допустимый набор значений этого столбца приведен в табл. 12.7.
Таблица 12.7. Возможные типы запроса SELECT, отображаемые в результате
выполнения оператора EXPLAIN
Тип Описание
SIMPLE Обычный старый тип запроса SELECT, как в рассматриваемом примере.
PRIMARY Внешний (первый) запрос, в котором используются подзапросы и соединения.
UNION Второй или последний запрос в соединении
DEPENDENT UNION Второй или последний запрос в соединении, зависящий от первичного запроса.
SUBQUERY Внутренний подзапрос.
DEPENDENT SUBQUERY Внутренний подзапрос, зависящий от первичного запроса (то есть связанный подзапрос).
DERIVED Подзапрос, использованный в выражении FROM.
Столбец table содержит список всех таблиц, которые использовались для фор-
мирования ответа на запрос. Каждая строка результата предоставляет дополни-
тельную информацию о способе использования конкретной таблицы в этом запро-
се. В данном случае мы видим, что в запросе использовались таблицы orders.
order_items, customers и books. (Это должно быть известно из самого запроса.)
Столбец type содержит пояснения о способе использования таблицы в соедине-
ниях внутри запроса. Набор возможных значений этого столбца представлен в табл.
12.8. Значения приведены в порядке уменьшения скорости выполнения запроса. Эта
таблица позволяет получить представление о количестве строк, которые должны
быть считаны из каждой таблицы для выполнения запроса.
В предыдущем примере две таблицы соединены с использованием eq_ref (orders
и customers), одна — посредством index (order_iterns) и еще одна (books) — с помо-
щью ALL — то есть за счет просмотра каждой отдельной строки таблицы.
Столбец rows дополняет эту информацию: он отображает приблизительное коли-
чество строк каждой таблицы, которые должны быть просмотрены для выполнения
этого соединения. Для выяснения общего числа строк, просматриваемых во время
выполнения запроса, можно перемножить эти числа. Это обусловлено тем, что со-
единение подобно произведению строк различных таблиц. Более подробно эти во-
просы рассматриваются в главе 10. Не забывайте, что это значение отражает количе-
ство просматриваемых, а не возвращаемых строк; MySQL не может вычислить точное
количество строк, не выполнив запрос.
Понятно, что чем меньшим удастся сделать это число, тем лучше. В настоящее
время база данных содержит ничтожный объем данных, но по мере увеличения объ-
ема время выполнения этого запроса также будет увеличиваться. Вскоре мы вернемся
к этому вопросу.
314
Часть II. Использование MySQL
Таблица 12.8. Возможные типы соединения, отображаемые в результате
выполнения EXPLAIN
Тип Описание
const или system Таблица считывается только однажды. Это имеет место, если таблица содержит только одну строку. Тип system используется, если данная таблица является системной, а тип const — во всех других случаях.
eq_ref Для каждого набора строк из других таблиц, участвующих в соедине- нии. выполняется считывание одной строки изданной таблицы. Этот тип применяется, когда для соединения используются все части индек- са таблицы, и индекс имеет тип UNIQUE (уникальный ключ) или являет- ся первичным ключом.
ref Для каждого набора строк из других таблиц, участвующих в соедине- нии, выполняется считывание набора строк таблицы, соответствую- щих критерию отбора. Этот тип применяется, когда условие соедине- ния не позволяет выбрать одну строку — то есть, когда в соединении используется только часть ключа, либо он не является ключом типа UNIQUE или первичным ключом.
ref_or_null Этот запрос подобен запросу ref, но для его выполнения MySQL ищет также строки NULL. (Этот тип в основном используется в подзапросах.)
index—merge Свидетельствует об использовании специфической оптимизации Index Merge (Слияние индексов).
un ique_subquery Этот тип соединения используется вместо соединения ref в некоторых подзапросах IN, возвращающих одну уникальную строку.
index_subquery Этот тип соединения аналогичен соединению unique_subquery, но используется для индексированных неуникальных подзапросов.
range Для каждого набора строк из других таблиц, которые участвуют в со- единении, выполняется считывание набора строк таблицы, относя- щихся к конкретному диапазону.
index Осуществляется сканирование всего индекса.
ALL Осуществляется сканирование всех строк таблицы.
Столбец possible_keys, как можно догадаться по его названию (возможные—ключи),
содержит имена ключей, которые MySQL может использовать для соединения таб-
лиц. Несложно заметить, что в данном случае возможными ключами являются все
ключи типа PRIMARY.
Столбец key содержит либо ключ таблицы, действительно использованный
MySQL, либо значение NULL, если ключи вообще не использовались. Обратите вни-
мание, что несмотря на существование допустимого первичного ключа для таблицы
books, он не был задействован в данном запросе.
Столбец key_len отображает длину использованного ключа. Это число служит для
определения того, использовалась ли только часть ключа. Упомянутая информация
важна при наличии ключей, которые состоят из более чем одного столбца. В данном
случае все использованные ключи были полными.
Столбец ref содержит информацию о столбцах, которые использовались с клю-
чом для выбора строк из таблицы.
Глава 12. Дополнительные сведения по администрированию MySQL
315
И, наконец, столбец Extra содержит всю дополнительную информацию о способе
выполнения соединения. Возможные значения этого столбца перечислены в табл. 12.9.
Таблица 12.9. Возможные значения столбца Extra в результате
выполнения оператора EXPLAIN
Значение Описание
Distinct После нахождения первой совпадающей строки MySQL прекращает дальнейшие попытки поиска строк.
Not exists Запрос оптимизирован для выполнения соединения LEFT JOIN.
Range checked Для каждой строки в наборе строк из других таблиц MySQL пытается
for each record найти наилучший для использования индекс, если таковые существуют.
Using filesort Для сортировки данных требуется два прохода. (Понятно, что выпол- нение этой операции требует в два раза больше времени.)
Using index Вся информация о таблице поступает из индексов; то есть, в действи- тельности просмотр строк не выполняется.
Using temporary Для выполнения данного запроса требуется создание временной таблицы.
Using where Для выбора строк используется выражение WHERE.
Существует несколько возможных способов решения проблем, обнаруженных на
основе анализа результатов выполнения EXPLAIN. Во-первых, можно проверить типы
столбцов и убедиться в том, что они одинаковы. В частности, это относится к ширине
столбцов. Индексы нельзя использовать для сопоставления столбцов, если те имеют
различную ширину. Эту проблему можно решить, изменяя типы столбцов, или закла-
дывая это в архитектуру таблиц с самого начала.
Во-вторых, можно указать оптимизатору соединения о необходимости проверять
распределение ключей и тем самым эффективнее оптимизировать соединения с по-
мощью утилиты myisamchk или оператора ANALYZE TABLE, которые эквивалентны.
Эту утилиту можно вызвать с помощью следующей команды:
myisamchk --analyze путь_к_базе^цанных_Му5д1/таблица
Несколько таблиц можно проверять, перечисляя их в командной строке, или ис-
пользуя команду:
myisamchk --analyze путь_к_базе^цаннь1х_Му5д1/*.MYI
Для проверки всех таблиц во всех базах данных можно воспользоваться такой ко-
мандой:
myisamchk --analyze путь_к_каталогу_данных__МуЗд1' */*.MYI
Альтернативный способ выполнения этой задачи состоит в перечислении таблиц
в операторе ANALYZE TABLE в среде монитора MySQL:
analyze table customers, orders, order_items, books;
В-третьих, можно рассмотреть целесообразность добавления нового индекса в
таблицу. Если данный запрос, во-первых, выполняется медленно и, во-вторых, ис-
пользуется достаточно часто, следует серьезно подумать о применении этого способа
решения проблем. Если же данный запрос лишь одноразовый, который никогда не
316
Часть II. Использование MySQL
будет выдан снова (например, загадочный отчет, который был запрошен лишь одна-
жды), реализация данного способа вряд ли будет стоить затраченных усилий, по-
скольку он замедлит выполнение других операций. Использование упомянутой тех-
нологии рассматривается в следующем разделе.
Ускорение выполнения запросов за счет
использования индексов
Если столбец possible_keys в результатах выполнения оператора EXPLAIN содер-
жит ряд значений NULL скорость выполнения запроса, возможно, удастся повысить,
добавив индекс в соответствующую таблицу. Если столбец, используемый в выраже-
нии WHERE, пригоден для индексации, новый индекс для него можно создать с помо-
щью оператора ALTER TABLE:
ALTER TABLE таблица ADD INDEX (столбец);
Оптимизация базы данных
В дополнение к ранее предложенным рекомендациям по оптимизации запросов,
можно предпринять и дополнительные действия для повышения общей производи-
тельности базы данных MySQL.
Оптимизация проекта
В основном, все элементы базы данных должны быть как можно меньшими. В оп-
ределенной степени этого можно добиться, выполняя нисходящее проектирование,
которое позволяет снизить избыточность. Везде, где возможно, следует минимизи-
ровать использование значений NULL и обеспечить, чтобы первичный ключ был мак-
симально коротким.
По возможности следует избегать использования столбцов переменной длины
(таких как VARCHAR, TEXT и BLOB). Таблицы с полями фиксированной длины будут об-
рабатываться быстрее, но они могут занимать несколько большее дисковое про-
странство.
Разрешения
Помимо использования рекомендаций, которые приведены в предыдущем разде-
ле, посвященном оператору EXPLAIN, скорость выполнения запросов можно увели-
чить за счет упрощения разрешений. Ранее мы уже рассматривали способ проверки
запросов системой разрешений перед их выполнением. Чем проще этот процесс, тем
быстрее будет выполняться запрос.
Оптимизация таблиц
Если таблица используется в течении некоторого времени, по мере обновления и
удаления данных она становится фрагментированной. Фрагментация увеличивает
время, необходимое для поиска данных в таблице. Эту проблему можно решить с по-
мощью следующего оператора:
Глава 12. Дополнительные сведения по администрированию MySQL
317
OPTIMIZE TABLE имя_таблицы;
или команды, которая должна вводиться в командной строке:
myisamchk -г имя_таблицы
Можно также с помощью утилиты myisamchk отсортировать индекс таблицы и
данные в соответствии с этим индексом:
myisamchk —sort-index —sort-records=l путь_к_каталогу_данных_Му5СЫ * / *.MYI
Использование индексов
Когда это требуется, для ускорения выполнения запросов следует пользоваться
индексами. Индексы должны быть максимально простыми. Не создавайте индексы,
которые не будут задействованы в запросах. Для проверки того, какие индексы ис-
пользуются в запросе, служит оператор EXPLAIN, как описано в предыдущих разделах.
Использование значении, заданных по умолчанию
Всегда, когда это возможно, для столбцов необходимо использовать значения, за-
данные по умолчанию, и вставлять данные только в том случае, если они отличаются
от этих значений. В результате уменьшается время, необходимое для выполнения
оператора INSERT.
Дополнительные советы
Для повышения производительности в конкретных ситуациях и решения отдель-
ных проблем можно произвести множество мелких настроек. Дополнительные советы
по этому поводу представлены на Web-сайте MySQL по адресу http://www. mysql. com.
Резервное копирование базы данных MySQL
MySQL предоставляет несколько способов выполнения резервного копирования.
Первый из них предполагает блокировку таблиц на время копирования физических
файлов с помощью команды LOCK TABLES, которая имеет следующий синтаксис:
LOCK TABLES таблица тип_блокировки [, таблица тип_блокировки ...]
Каждый параметр таблица должен быть именем таблицы, а значением параметра
тип_блокировки должно быть либо READ либо WRITE. Для резервного копирования
необходима блокировка чтения (READ). Перед выполнением резервного копирования
потребуется выполнить команду FLUSH TABLES; для гарантированной записи на диск
любых изменений в индексах.
Во время резервного копирования пользователи и сценарии все еще могут выпол-
нять запросы только для чтения. Поэтому, при наличии большого числа запросов,
изменяющих базу данных, например, заказов, которые поступают от клиентов, по-
добное решение не годится.
Второй и более совершенный способ состоит в использовании команды
mysql_dump. Она запускается из командной строки и, как правило, выглядит как-то так:
mysqldump --opt --all-databases > all.sql
318
Часть II. Использование MySQL
Эта команда записывает все SQL-команды, необходимые для восстановления базы
данных, в файл all. sql.
Затем потребуется временно остановить процесс mysqld и перезапустить его с па-
раметром --1од-Ып[=файл_журнала]. Обновления, хранящиеся в файле журнала,
позволяют получить данные изменений, выполненных с момента резервного копи-
рования. (Понятно, что в ходе каждого обычного резервного копирования необхо-
димо выполнять резервное копирование и файлов журнала.)
Третий метод предполагает использование сценария mysqlhotcopy. Он вызывает-
ся следующим образом:
mysqlhotcopy база_данных /путь/к/резервной_копии
Далее необходимо выполнить действия по запуск}' и останову базы данных, кото-
рые были описаны выше.
Последний (и наиболее надежный) метод выполнения резервного копирования
состоит в поддержании реплицированной копии базы данных. Создание реплициро-
ванных копий рассматривается далее в главе.
Восстановление базы данных MySQL
Для восстановления базы данных MySQL также существует несколько подхо-
дов. Если проблема связана с поврежденной таблицей, можно запустить команду
myisamchk с параметром -г (“repair” — восстановление).
Если для создания резервной копии использовался первый из описанных в пре-
дыдущем разделе методов, файлы данных можно скопировать в те же каталоги новой
установки MySQL.
Если для копирования применялся второй метод, придется выполнить несколько
шагов. Вначале следует выполнить запросы, записанные в файле резервной копии.
Это позволит воссоздать базу данных на момент сохранения. Затем потребуется об-
новить базу данных до состояния, которое было сохранено в бинарном журнале. Это
можно сделать с помощью следующей команды:
mysqlbinlog имя_хоста-Ъ1п.[0-9]* | mysql
Дополнительную информацию о процессе резервного копирования и восстанов-
ления баз данных MySQL можно найти на Web-сайте MySQL по адресу:
http://www.mysql.com
Реализация репликации
Репликация — это технология, которая позволяет использовать несколько серве-
ров баз данных для обработки одних и тех же данных. Она позволяет загружать со-
вместно используемые данные и повышает надежность системы; если один из серве-
ров выходит из строя, остальные серверы будут продолжать обслуживать запросы.
После развертывания такой системы ее можно использовать также для целей резерв-
ного копирования.
Основная идея этого подхода заключается в том, что организуется ведущий сервер
репликации и несколько ведомых серверов. Каждый из ведомых серверов является
Глава 12. Дополнительные сведения по администрированию MySQL
319
зеркальным отражением ведущего. При первоначальной настройке ведомых серве-
ров, на них копируется снимок всех данных, хранящихся на ведущем сервере в дан-
ный момент времени. После этого ведомые серверы запрашивают обновления с ве-
дущего сервера. Ведущий сервер передает информацию о запросах, которые были
выполнены, из своего бинарного журнала, а ведомые сервера применяют этч инфор-
мацию к данным.
Обычный способ использования этой технологии — применение запросов записи к
ведущему' серверу и запросов чтения — к ведомым. Этот подход реализуется через биз-
нес-логику приложения. Возможно использование и более сложной архитектуры, на-
пример, нескольких ведущих серверов, но мы рассмотрим только типичный пример.
Следует отметить, что, как правило, в отличие от ведущего, ведомые серверы со-
держат не самые свежие данные. Подобное несоответствие имеет место в любой рас-
пределенной базе данных.
Чтобы приступить к развертыванию архитектуры с применением ведущего и ве-
домых серверов, потребуется включить ведение бинарного журнала на ведущем сер-
вере. Вопросы активизации бинарного журнала рассматриваются в приложении А.
Необходимо также внести изменения в файлы my. ini или ту. cnf на ведущем и ве-
домых серверах. На ведущем сервере файл должен содержать следующие записи:
[mysqld]
log-bin
server-id=l
Первая запись включает ведение бинарного журнала (следовательно, она должна
уже присутствовать; в противном случае добавьте ее). Вторая запись присваивает ве-
дущему серверу уникальный идентификатор. Каждый из ведомых серверов также ну-
ждается в идентификаторе, поэтому аналогичную строку следует добавить и в файлы
my. ini/ту .cnf на каждом из ведомых серверов. Убедитесь в том, что номера уни-
кальны! Например, первый ведомый сервер может содержать запись server-id=2;
следующий — server-id=3; и так далее.
Настройка ведущего сервера
На ведущем сервере необходимо создать пользователя, под именем которого к
нему' будут подключаться ведомые серверы. Для них существует специальный в ровень
полномочий, называемый ведомым сервером репликации (replication slave). В зависимо-
сти от того, как предполагается выполнять первоначальную передаче- данных, им,
возможно, придется временно предоставить ряд дополнительных полномочий.
В большинстве случаев для передачи данных будет использоваться снимок базы
данных, и в этом случае необходимы только специальные полномочия ведомого сер-
вера репликации. Если для передачи данных будет применяться команда LOAD DATA
FROM MASTER (она описана в следующем разделе), этому пользователю требуются так-
же полномочия RELOAD, SUPER и SELECT, но только для выполнения начальной на-
стройки. В соответствие с принципом наименьших полномочий, эти полномочия
нужно будет отозвать после успешного запуска системы.
Создайте пользователя на ведущем сервере. Ему можно присвоить любое имя и
любой пароль, однако эту информацию потребуется запомнить или записать. В на-
шем примере мы назвали этого пользователя rep_slave:
320
Часть II. Использование MySQL
grant replication slave
on * . *
to 'rep_slave'@'%' identified by 'пароль';
Понятно, что пароль нужно заменить на какой-то более приемлемый.
Выполнение первоначальной передачи данных
Передачу данных с ведущего сервера на ведомый можно выполнить несколькими
способами. Проще всего установить ведомые серверы (этот процесс описан в сле-
дующем разделе), а затем выполнить оператор LOAD DATA FROM MASTER. Проблема,
связанная с применением этого подхода, состоит в том, что он блокирует таблицы на
ведущем сервере на время передачи данных, которое может оказаться достаточно
длительным. Поэтому использовать его не рекомендуется. (Его можно применять
только в отношении таблиц MylSAM.)
В общем случае лучше получить снимок базы данных на конкретный момент вре-
мени. Это делается с помощью процедур создания резервных копий, которые описа-
ны в предыдущих разделах этой главы:
flush tables with read lock;
Причина применения блокировки чтения связана с необходимостью записать по-
зицию сервера в бинарном журнале на момент получения снимка базы данных. Это
можно выполнить с использованием следующего оператора:
show master status;
Полученный вывод должен быть аналогичен показанному ниже:
+-------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------+----------+--------------+------------------+
| laura-ltc-bin.000001 | 95 | | |
Обратите внимание на столбцы File (Файл) и Position (Позиция); эта информа-
ция потребуется во время установки ведомых серверов.
Теперь получите снимок и разблокируйте таблицы, выполнив такой оператор:
unlock tables;
В случае использования таблиц InnoDB проще всего воспользоваться утили-
той InnoDB Hot Backup, которая доступна на сайте Innobase Оу по адресу
http: / /www. innodb. com. Этот инструмент не является бесплатным, поэтому придется
заплатить за лицензию. В качестве альтернативы, можно прибегнуть к ранее описан-
ной процедуре и, прежде чем снимать блокировку таблиц, остановить сервер, MySQL
и скопировать весь каталог базы данных, предназначенной для репликации, после
чего перезапустить сервер и разблокировать таблицы.
Настройка одного или нескольких ведомых серверов
Доступны два варианта настройки одного или нескольких ведомых серверов. Если
ранее был получен снимок базы данных, начните с его установки на ведомом сервере.
Затем на ведомом сервере выполните следующие запросы:
Глава 12. Дополнительные сведения по администрированию MySQL
321
change master to
master-host-'сервер',
master-user=1 пользователь' ,
master-password^’пароль',
master-log-file='файл_журнала',
mas t er -1 og-pos=позиция_в_журнале;
start slave;
Вместо заполнителей, выделенных курсивом, необходимо подставить конкретные
значения. Заполнитель сервер - это имя ведущего сервера. Значения пользователь и
пароль должны соответствовать указанным в операторе GRANT, который выполнялся
на ведущем сервере. Значения файл_журнала и позиция_в_журнале соответствуют
приведенным в выводе оператора SHOW MASTER STATUS, который был выполнен на
ведущем сервере.
Теперь система должна быть настроена и нормально функционировать.
Если снимок базы данных не был получен, после выполнения предыдущего запро-
са данные можно загрузить с ведущего сервера с помощью следующего оператора:
load data from master;
Дополнительные источники информации
В главах, посвященных MySQL, основное внимание было уделено тем применени-
ям и компонентам системы, которые имеют наибольшее значение для разработки
Web-приложений и для связывания MySQL и РНР. Дополнительную информацию по
вопросам администрирования MySQL можно получить на Web-сайте MySQL по адресу
http://www.mysql.com.
Полезную информацию можно почерпнуть также из книг MySQL. Руководство ад-
министратора (Издательский дом “Вильямс”, 2005) или третьего издания книги Поля
Дюбуа (Paul Dubois) MySQL (Издательский дом “Вильямс”, 2005).
Что дальше
В следующей главе мы рассмотрим более сложные функциональные возможности
MySQL, которые полезны при разработке Web-приложений, в том числе способы ис-
пользования различных механизмов хранения, транзакций и хранимых процедур.
322
Часть II. Использование MySQL
13
Дополнительные сведения
по программированию
в MySQL
В этой главе рассматриваются более сложные вопросы, связанные с MySQL,
включая типы таблиц, транзакции и хранимые процедуры.
В главе, помимо прочих, рассматриваются следующие темы:
Оператор LOAD DATA INFILE.
Механизмы хранения.
Транзакции.
Внешние ключи.
Хранимые процедуры.
Оператор LOAD DATA INFILE
Одной из ранее не рассматриваемых полезных функций MySQL является опера-
тор LOAD DATA INFILE. Его можно использовать для загрузки данных из файла. Этот
оператор выполняется очень быстро.
Эта предоставляющая множество возможностей команда принимает набор пара-
метров; типичная форма ее применения выглядит следующим образом:
LOAD DATA INFILE "newbooks.txt" INTO TABLE books;
Показанная строка кода считывает данные из файла newbooks.txt в таблицу
books. По умолчанию поля данных в файле должны разделяться символами табуля-
ции и быть заключены в одинарные кавычки, а строки должны разделяться символом
новой строки (\п). Специальные символы должны быть отменены с помощью симво-
ла косой черты (\). Все эти характеристики доступны для конфигурирования через
параметры оператора LOAD; для получения более подробной информации обратитесь
к руководству по MySQL.
Чтобы иметь возможность использовать оператор LOAD DATA INFILE, пользователь
должен обладать полномочиями FILE, описанными в главе 9.
Механизмы хранения
MySQL поддерживает различные механизмы хранения, которые иногда называ-
ются также типами таблиц. Это означает, что программист может выбирать внутрен-
нюю реализацию таблиц. Каждая таблица в базе данных может использовать свой
механизм хранения, причем MySQL обеспечивает простое преобразование из одного
механизма в другой.
Тип таблицы можно выбрать во время ее создания с помощью следующего оператора:
CREATE TABLE имя_габлицы ТЧР&=тип ...
Возможны следующие типы таблиц:
MylSAM. Этот тип используется по умолчанию, и именно он применялся до
сих пор. Он построен на основе традиционного типа ISAM, название которого
представляет собой аббревиатуру от Indexed Sequential Access Method (индексно-
последовательный метод доступа) — стандартного метода хранения записей и
файлов. В отличие от ISAM, MylSAM предоставляет ряд дополнительных пре-
имуществ. По сравнению с другими механизмами хранения MylSAM обеспечи-
вает наибольшее количество средств проверки и восстановления таблиц. Таб-
лицы MylSAM допускают сжатие и поддерживают полнотекстовый поиск. Тем
не менее, они не обеспечивают безопасное выполнение транзакций и не под-
держивают внешние ключи.
ISAM. Этот тип описан в предыдущем йункте. Пользоваться таблицами ISAM не
рекомендуется.
MEMORY (ранее этот тип называли НЕАР).Таблицы этого типа хранятся в
памяти и их индексы хешируются. Обработка таблиц MEMORY выполняется
очень быстро, однако любой сбой приводит к потере данных. Перечисленные
характеристики делают таблицы типа MEMORY идеально подходящим для
хранения временных данных или данных, полученных в результате вычисле-
ний. При использовании этого типа в операторе CREATE TABLE необходимо
указывать параметр MAX_ROWS (максимальное число строк); в противном случае
эти таблицы могут занять всю память. Кроме того, эти таблицы не могут со-
держать столбцы типа BLOB, TEXT или AUTO INCREMENT.
MERGE. Эти таблицы позволяют при запросах выполнять обработку коллекции
таблиц MylSAM как единой таблицы. В результате удается обходить ограничения
на максимальный размер таблиц в некоторых операционных системах.
BDB. Эти таблицы обеспечивают безопасное выполнение транзакций, то
есть они предоставляют возможность использования операторов COMMIT и
ROLLBACK. Их обработка выполняется медленнее, чем таблиц MylSAM, но зато
они задействуют все преимущества транзакций. Эти таблицы построены на ос-
нове базы данных Berkeley DB.
InnoDB. Эти таблицы также обеспечивают безопасное выполнение транзак-
ций, и в отношении них справедливы те же соображения, что и по поводу таб-
лиц BDB. Они также поддерживают внешние ключи. Обработка таблиц InnoDB
выполняется быстрее, и они предоставляют большие функциональные воз-
можности, нежели таблицы BDB, поэтому мы рекомендуем их использовать в
тех случаях, когда требуется обеспечить безопасное выполнение транзакций.
324
Часть II. Использование MySQL
В большинстве случаев в Web-приложениях придется применять таблицы MylSAM
или InnoDB, либо их комбинацию.
Таблицы MylSAM следует использовать в тех случаях, когда в таблицах прихо-
дится выполнять много операций SELECT или INSERT (но не и тех и других одновре-
менно), поскольку они обеспечивают наиболее быстрое их выполнение. Для мно-
гих Web-приложений, таких как каталоги, MylSAM является лучшим выбором.
Таблицы MylSAM следует применять и тогда, когда необходимы возможности полно-
текстового поиска. Таблицы InnoDB следует использовать в тех случаях, когда важно
обеспечить выполнение транзакций, например, в таблицах финансовых данных или
в ситуациях поочередного применения операций INSERT и SELECT (например, на се-
тевых досках объявлений или форумах).
Тип MEMORY можно использовать для временных таблиц или для реализации
представлений, а тип MERGE — когда приходится иметь дело с очень большими таб-
лицами MylSAM.
После создания тип таблицы .можно изменить с помощью оператора ALTER TABLE,
как показано в следующем примере:
alter table orders type=innodb;
alter table order_items type=innodb;
В большинстве примеров в этой книге применяются таблицы MylSAM. А теперь
уделим немного времени использованию транзакций и способам их реализации в
таблицах InnoDB.
Транзакции
Транзакции — это механизмы обеспечения целостности баз данных, особенно в
случаях ошибок или отказа сервера. В последующих разделах вы узнаете, что собой
представляют транзакции, и как их можно реализовать с помощью таблиц InnoDB.
Определения транзакций
Прежде всего, дадим определение термину транзакция. Транзакция — это запрос
или набор запросов, который гарантированно будет выполнен в базе данных полно-
стью, либо не будет выполнен вовсе. В результате база данных сохраняет свою цело-
стность независимо от успешности завершения транзакции.
Чтобы понять, почему эта возможность может оказаться настолько важной, рас-
смотрим банковскую базу данных. Представьте себе ситуацию, в которой нужно вы-
полнить перевод денег с одного счета на другой. Это действие предполагает снятие
денег с одного счета и помещение их на другой, для чего потребуется выполнение,
как минимум двух запросов. Что произойдет, если деньги будут сняты с одного счета,
а напряжение питания пропадет прежде, чем они будут помещены на другой счет?
Означает ли это, что деньги просто “исчезнут”?
Возможно, вы сталкивались с понятием соответствия ACID. ACID (atomicity, consis-
tency, isolation, durability — атомарность, целостность, изоляция, постоянство) пред-
ставляет собой способ описания четырех требований, которым должны удовлетво-
рять транзакции:
Глава 13. Дополнительные сведения по программированию в MySQL
325
Атомарность. Транзакция должна быть атомарной, то есть либо выполняться
полностью, либо не выполняться вообще.
Целостность. Транзакция должна оставлять базу данных в целостном со-
стоянии.
Изоляция. Незавершенные транзакции не должны быть видимы другим поль-
зователям базы данных; то есть до тех пор, пока они не завершены, транзакции
должны оставаться изолированными.
Постоянство. После сохранения в базе данных результаты выполнения тран-
закции должны быть долговременными или постоянными.
Транзакция, результат выполнения которой записан в базу данных, называют под-
твержденной. Транзакция, результат выполнения которой не записан в базу данных —
то есть база данных возвращена в то состояние, каком она была до начала выполне-
ния транзакции — называют откатанной транзакцией.
Использование транзакций в сочетании
с таблицами InnoDB
По умолчанию MySQL работает в режиме автоматического подтверждения. Это озна-
чает, что результат каждого выполненного оператора немедленно записывается в
базу данных (подтверждается). При использовании типа таблиц, поддерживающего
безопасное выполнение транзакций, такое поведение, скорее всего, будет неприем-
лемым.
Чтобы в текущем сеансе отключить режим автоматического подтверждения, вве-
дите следующую команду:
set autocommit=0;
Если режим автоматического подтверждения включен, транзакция должна начи-
наться с оператора
start transaction;
Если упомянутый режим отключен, эта команда не нужна, поскольку транзакция
будет автоматически запущена после ввода оператора SQL.
По завершении ввода операторов, образующих транзакцию, их можно подтвер-
дить в базе данных с помощью следующей команды:
commit;
Если же вы передумали, к предыдущему состоянию базы данных можно вернуться,
набрав команду:
rollback;
До тех пор, пока транзакция не была подтверждена, она не будет видима другим
пользователям или в других сеансах.
Рассмотрим пример. Если это еще не было сделано, выполните приведенные в
предыдущем разделе операторы ALTER TABLE применительно к базе данных books,
как показано в следующем примерю:
alter table orders type=innodb;
alter table order_items type=innodb;
326
Часть II. Использование MySQL
Эти операторы преобразуют две таблицы в таблицы InnoDB. (Впоследствии при
желании можно будет выполнить обратное преобразование с помощью того же опе-
ратора, но на этот раз с парамегром type=MyISAM.)
Теперь откройте два соединения с базой данных books. В одном соединении до-
бавьте в базу данных новую запись заказа:
insert into orders values
(5, 2, 69.98, '2005-06-18') ;
insert into order_items values
(5, '0-672-31697-8', 1) ;
Теперь посмотрите, видим ли новый заказ:
select * from orders where orderid=5;
Заказ должен быть присутствовать в выходной информации:
| orderid | customerid ; amount | date |
| 5 | 2 69.98 | 2005-06-18 |
Оставив текущее соединение открытым, перейдите к другом}' соединению и вы-
полните такой же запрос select. Заказ не должен отображаться в выводе:
Empty set (0.00 sec)
(Если он все же отображается, скорее всего, вы забыли отключить режим автома-
тического подтверждения. Проверьте это, а также то, что таблица была преобразо-
вана в формат InnoDB.) Это обусловлено тем, что транзакция еще не подтверждена.
(Приведенный пример хорошо иллюстрирует действие изоляции транзакций.)
Вернитесь к первому соединению и подтвердите транзакцию:
commit;
Теперь соответствующая строка должна отображаться в выводе и во втором со-
единении.
Внешние ключи
Тип InnoDB поддерживает также внешние ключи. Как вы, вероятно, помните, мы
рассматривали концепцию внешних ключей в главе 8. При использовании таблиц
типа MylSAM применение внешних ключей невозможно.
Например, рассмотрим вставку строки в таблицу order_items. При этом необхо-
димо указывать допустимое значение столбца orderid. В случае использования таб-
лицы MylSAM допустимость вставляемого значения orderid приходится проверять
где-то в коде приложения. Применение внешних ключей в таблице InnoDB позволяет
выполнять эту проверку в базе данных.
Рассмотрим, как определяются внешние ключи. Чтобы создать таблицу, которая
использует внешний ключ с самого начала, можно следующим образом изменить со-
ответствующий DDL-оператор:
Глава 13. Дополнительные сведения по программированию в MySQL
327
create table order_items
( orderid int unsigned not null references orders(orderid),
isbn char(13) not null,
quantity tinyint unsigned,
primary key (orderid, isbn)
) type=InnoDB;
После orderid мы добавили слова references orders(orderid). Это означает,
что данный столбец является внешним ключом, который должен содержать значение
из столбца orderid таблицы orders.
Кроме того, в конец объявления помещен тип таблицы type=InnoDB. Это требует-
ся для обеспечения работы внешних ключей.
С помощью операторов ALTER TABLE эти же изменения можно внести в сущест-
вующую таблицу:
alter table order_items type=InnoDB;
alter table order_items
add foreign key (orderid) references orders(orderid);
Чтобы убедиться в работе этого изменения, можно попытаться вставить стро-
ку, содержащую значение поля ordered, которое не имеет соответствия в таблице
orders:
insert into order_items values
(77, '0-672-31697-8', 7);
Вы должны получить сообщение об ошибке, аналогичное следующему:
ERROR 1216 (23000) : Cannot add or update a child row:
a foreign key constraint fails
ОШИБКА 1216 (23000): Невозможно добавить или обновить дочернюю строку:
ошибка ограничения внешнего ключа
Хранимые процедуры
В версии MySQL 5, которая на момент написания книги проходила этап альфа-
тестирования, появились хранимые процедуры (stored procedures). В 2005 году эта вер-
сия должна быть доступна на рынке. В последующих разделах мы бегло ознакомимся
с хранимыми процедурами, которые станут основной новой функциональной воз-
можностью версии 5.0.
Хранимая процедура — это программная функция, которая создается и хранится в
базе данных MySQL. Она может состоять из SQL-операторов и ряда специальных
управляющих структур. Хранимая функция может быть полезна, когда одну и ту же
функцию необходимо выполнять из различных приложений или с различных плат-
форм, либо в качестве средства инкапсуляции функциональных возможностей. Хра-
нимые процедуры в базе данных можно считать аналогом объектно-ориентирован-
ного подхода в программировании. Они позволяют управлять способом доступа к
данным.
Простой пример
Объявление хранимой процедуры показано в листинге 13.1.
328
Часть II. Использование MySQL
Листинг 13.1. basic stored -procedure.sql — объявление хранимой процедуры
# Простой пример хранимой процедуры
delimiter //
create procedure total_orders (out total float)
BEGIN
select sum(amount) into total from orders;
END
//
delimiter ;
Рассмотрим этот код строка за строкой.
Первый оператор:
delimiter //
изменяет ограничитель конца оператора с текущего значения — как правило, точки с
запятой, если это не было изменено ранее — на два символа косой черты. Это необхо-
димо для того, чтобы ограничитель в виде точки с запятой можно было использовать
внутри хранимой процедуры при вводе ее кода, и чтобы MySQL не пытался выпол-
нять код во время ввода.
Следующая строка:
create procedure total_orders (out total float)
создает собственно хранимую процедуру. Ей присваивается имя total_orders. Она
содержит единственный параметр total — значение, которое будет вычисляться.
Слово OUT показывает, что этот параметр должен передаваться или возвращаться.
Параметры могут быть объявлены также как IN, что означает, что значение пере-
дается внутрь процедуры, либо как INOUT, что означает, что значение передается в
процедуру, но может ею изменяться.
Слово float задает тип параметра. В данном случае мы возвращаем сумму всех за-
казов в таблице orders. Тип столбца orders — float, поэтому возвращаемым типом
также является float. Допустимые типы данных преобразуются в доступные типы
столбцов.
Если хранимой процедуре нужно передать более одного параметра, это можно де-
лать с помощью разделенного запятыми списка, как в коде РНР.
Тело процедуры размещается между операторами BEGIN и END. Они аналогичны
фигурным скобкам внутри кодаРНР ({}), поскольку ограничивают блок операторов.
В теле просто выполняется оператор SELECT. Единственное отличие от обычного
использования этого оператора состоит в том, что в него включено выражение into
total для загрузки результата выполнения запроса в параметр total.
По завершении объявления процедуры в качестве ограничителя снова определя-
ется точка с запятой:
delimiter ;
После того как процедура объявлена, ее можно вызывать с помощью ключевого
слова call, как показано в следующем примере:
call total_orders(@t);
Глава 13. Дополнительные сведения по программированию в MySQL
329
Этот оператор вызывает хранимую процедуру total_orders и передает ей пере-
менную для сохранения результата. Чтобы увидеть результат, необходимо просмот-
реть содержимое переменной:
select @t;
Результат выполнения этой команды должен быть подобен показанному ниже:
+----------------+
I @t |
+----------------+
| 289.92001152039 |
+----------------+
Аналогично созданию процедуры можно создать функцию. Функция принимает
входные параметры (только входные) и возвращает единственное значение. (На мо-
мент написания книги функции не могли ссылаться на таблицы, но к моменту выхода
коммерческой версии 5.0 это ограничение должно быть снято.)
Общий синтаксис выполнения этой задачи почти такой же. Пример функции по-
казан в листинге 13.2.
Листинг 13.2. basic function. sql — объявление хранимой функции
# Общий синтаксис создания функции
delimiter //
create function add_tax (price float) returns float
return price*1.1;
//
delimiter ;
Как видите, в этом примере вместо ключевого слова procedure используется клю-
чевое слово function. Есть и несколько других различий.
Параметры не обязательно указывать как имеющие тип IN или OUT, поскольку все
они являются параметрами типа IN. или входными параметрами. За списком пара-
метров следует выражение returns float. Оно задает тип возвращаемого значения.
Это значение может- иметь любой из допустимых типов MySQL.
Возврат значения выполняется с помощью оператора return подобно тому, как
это делается в РНР.
Обратите внимание, что в этом примере операторы BEGIN и END не используются.
Их можно применять, однако они не обязательны. Как и в РНР, если блок операто-
ров содержит только один оператор, его начало и конец помечать не обязательно.
Вызов функции несколько отличается от вызова процедуры. Вызов хранимой
функции выполняется аналогично вызову встроенной функции. Например:
select add_tax(100);
Этот оператор должен вернуть следующий результат:
+-------------+
| add_tax(100) |
+-------------+
I по I
330
Часть II Использование MySQL
После того, как процедуры и функции определены, код, который был использован
для их определения, можно просмотреть, например, с помощью такого оператора:
show create procedure total_orders;
или
show create function addtax;
Их можно удалить с помощью оператора
drop procedure total_orders;
или
drop function add_tax;
Хранимые процедуры допускают использование управляющих структур, перемен-
ных, обработчиков DECLARE (подобно исключениям), а также важных компонентов,
называемых курсорами (cursors). Каждый из этих компонентов будет кратко рассмот-
рен в последующих разделах.
Локальные переменные
Локальные переменные можно объявить внутри блока begin. . end с помощью
оператора declare. Например, функцию add_tax можно было бы изменить, чтобы
для хранения налоговой ставки она использовала локальную переменную, как пока-
зано в листинге 13.3.
Листинг 13.3. basic function, sql — объявление хранимой функции с переменными
# Общий синтаксис создания функции
delimiter //
create function add_tax (price float) returns float
begin
declare tax float default 0.10;
return price*(1+tax);
end
//
delimiter ;
Как видите, для объявления переменной используется ключевое слово declare, за
которым следуют имя переменной и тип. Выражение default не обязательно и оно
указывает начальное значение переменной. Затем переменную можно использовать,
как обычно.
Курсоры и управляющие структуры
Рассмотрим более сложный пример. Для этого мы создадим хранимую процедуру,
которая выясняет, какой заказ имел максимальную общую стоимость, и возвращает
значение orderid. (Конечно, это значение достаточно легко можно было бы выяс-
нить с помощью единственного запроса, но этот простой пример иллюстрирует при-
менение курсоров и управляющих структур.) Код этой хранимой процедуры показан
в листинге 13.4.
Глава 13. Дополнительные сведения по программированию в MySQL
331
Листинг 13.4. control—structures—cursors. sql — использование курсоров и циклов
для вычисления результирующего набора
# Процедура для поиска orderid с максимальной суммой заказа.
# Эту задачу можно было вы выполнить с помощью функции max, но данная
# процедура служит лишь для иллюстрации принципов использования
# хранимых процедур
delimiter //
create procedure largest_order(out largest_id int)
begin
declare this_id int;
declare this_amount float;
declare l_amount float default 0.0;
declare l_id int;
declare done int default 0;
declare continue handler for sqlstate '02000' set done = 1;
declare cl cursor for select orderid, amount from orders;
open cl;
repeat
fetch cl into this_id, this_amount;
if not done then
if this_amount > l_amount then
set l_amount=this_amount;
set l_id=this_id;
end if;
end if;
until done end repeat;
close cl;
set largest_id=l_id;
end
//
delimiter ;
В этом коде задействованы управляющие структуры (как условия, так и циклы),
курсоры и обработчики объявлений. Рассмотрим его строка за строкой.
В начале процедуры мы объявляем несколько локальных переменных, которые
будут использоваться внутри нее. Переменные this_id и this_amount хранят значе-
ния полей orderid и amount текущей строки. Переменные l_amount и l_id служат для
хранения суммы максимального заказа и соответствующего идентификатора. По-
скольку максимальная сумма заказа будет вычисляться путем сравнения каждого зна-
чения с текущим наибольшим значением, этой переменной присвоено начальное
значение, равное нулю.
Следующая объявленная переменная — done, которой присвоено начальное зна-
чение, равное нулю (false). Эта переменная представляет собой флаг цикла. Когда
строк для просмотра больше не останется, ее значение будет установлено равным 1
(true).
332
Часть II. Использование MySQL
Строка
declare continue handler for sqlstate '02000' set done = 1;
называется обработчиком объявления (declare handler). В хранимых процедурах он по-
добен исключению. На момент написания книги можно было использовать обработ-
чики продолжения и обработчики выхода. Обработчики продолжения, подобные
приведенному выше, выполняют указанное действие, а затем продолжают выпол-
нение процедуры. Обработчики выхода выполняют выход из ближайшего блока
begin..end.
Следующая часть обработчика объявления указывает условие вызова этого обра-
ботчика. В данном случае он будет вызываться при достижении состояния sqlstate
' 02000 '. Это условие может казаться весьма загадочным. В действительности же оно
означает, что обработчик будет вызываться, если ни одна строка не найдена. Резуль-
тирующий набор обрабатывается строка за строкой, и когда строк для обработки не
остается, процедура вызовет этот обработчик. С таким же успехом можно было бы
указать FOR NOT FOUND. Другими возможными параметрами являются SQLWARNING и
SQLEXCEPTION.
Следующий компонент, с которым необходимо ознакомиться — курсор (cursor).
Курсор ничем не отличается от массива: он извлекает результирующий набор запроса
(такой, как возвращаемый функцией mysqli_query ()) и позволяет выполнить его
построчную обработку (как, например, с помощью функции mysqli_fetch_row()).
Рассмотрим следующий курсор:
declare cl cursor for select orderid, amount from orders;
Этот курсор имеет имя cl. Это всего лишь определение того, что он будет содер-
жать. Пока что запрос не будет выполняться.
Следующая строка:
open cl;
в действительности выполняет запрос. Для получения каждой строки данных потре-
буется выполнить оператор fetch. Это делается в цикле repeat. В данном случае цикл
выглядит следующим образом:
repeat
until done end repeat;
Обратите внимание, что условие (until done) не проверяется до завершения
процедуры. Хранимые процедуры поддерживают также циклы while, имеющие по-
казанную ниже форму:
while условие do
end while;
Существуют также циклы loop:
loop
end loop
Глава 13. Дополнительные сведения по программированию в MySQL
333
Эти циклы не имеют встроенных условий, но выход из них может быть выполнен
с помощью оператора leave;.
Обратите внимание на отсутствие циклов for.
Следующая строка в коде примера загружает строку' данных:
fetch cl into this_id, this_amount;
Эта строка извлекает строку' из запроса курсора. Два атрибута, полученные запро-
сом, сохраняются в двух указанных локальных переменных.
Затем с помощью двух операторов IF мы проверяем, была ли получена строка, по-
сле чего сравниваем текущую сумму цикла с максимальной сохраненной суммой заказа:
if not done then
if this_amount > l_amount then
set l_amount=this_amount;
set l_id=this_id;
end if;
end if;
Обратите внимание, что значения переменных устанавливаются с помощью опе-
ратора set.
Помимо структуры if., then хранимые процедуры поддерживают также конст-
рукцию if . . then. . else, которая имеет следующую форму:
if условие then
[elseif условие then]
[else]
end if
Можно использовать также оператор case, форма которого показана ниже:
case значение
when значение then оператор
[when значение then оператор ...]
[else оператор]
end case
Теперь вернемся к нашему примеру. После того как цикл прерван, необходимо
выполнить небольшую очистку':
close cl;
set largest_id=l_id;
Оператор close закрывает курсор.
И, наконец, мы устанавливаем значение параметра OUT равным вычисленному
значению. Параметр нельзя использовать в качестве временной переменной, а толь-
ко для хранения конечного значения. (Такое применение параметров аналогично их
использованию в ряде других языков программирования, подобных Ada.)
Если эта процедура была создана, как описано в данной главе, ее можно вызвать
как любую другую процедуру:
call largest_order(@1);
select @1;
334
Часть II. Использование MySQL
При этом вывод должен быть похож на показанный ниже:
+----+
I I
+----+
I 3 I
+---+
Можете сами проверить правильность результата.
Дополнительные источники информации
В этой главе мы кратко ознакомились с функционированием и применением хра-
нимых процедур. Дополнительную информацию о хранимых процедурах можно по-
лучить в руководстве по MySQL. Хотя на момент написания книги руководство со-
держало не слишком много материала по данному' вопросу, можно не сомневаться,
что прежде чем эта версия MySQL станет коммерческой, документация приобрегет
более совершенный вид.
Для получения дополнительной информации по оператору LOAD DATA INFILE, раз-
личным механизмам хранения и хранимым процедурам обратитесь к руководству по
MySQL.
Тем, кого интересуют вопросы транзакций и целостности баз данных, мы реко-
мендуем ознакомиться с одной из фундаментальных работ по реляционным базам
данных — книге К. Дж. Дейта (С. J. Date) Введение в системы баз данных, 8-е издание (Из-
дательский дом “Вильямс”, 2005).
Что дальше
Вы ознакомились с основами применения РНР и MySQL. В главе 14 будут рассмот-
рены аспекты настройки Web-сайтов, использующих базы данных, которые связаны с
электронной коммерцией и безопасностью.
Глава 13. Дополнительные сведения по программированию в MySQL
335
Ill
Электронная коммерция
и безопасность
Глава 14. Эксплуатация сайта электронной коммерции
Глава 15. Безопасность сайта электронной коммерции
Глава 16. Реализация задачи аутентификации
с помощью РНР и MySQL
Глава 17. Реализация безопасных транзакций
с помощью РНР и MySQL
14
Эксплуатация сайта
электронной
коммерции
В этой главе рассматриваются некоторые вопросы, связанные со спецификацией,
проектированием, созданием и эксплуатацией сайта электронной коммерции.
В ней будут рассмотрено планирование, возможные риски и методы достижения са-
моокупаемости Web-сайта.
В главе, помимо прочих, рассматриваются следующие темы:
Определение целей, преследуемых сайтом электронной коммерции.
Типы коммерческих Web-сайтов.
Риски и угрозы.
Выбор стратегии.
Определение целей, которые должны
быть достигнуты
11 режде чем с головой погрузиться в разработку Web-сайта и всех составляющих
его компонентов, следует уяснить для себя задачи, которые призван решать созда-
ваемый сайт, и уже исходя из этого, составить более или менее подробный план ре-
шения таких задач.
В данной книге мы будем исходить из предположения, что строится именно ком-
мерческий Web-сайт. Следовательно, одна из основных задач заключается в получе-
нии прибыли.
Существует немало способов коммерческого подхода к Internet. Через Internet
можно рекламировать стандартные услуги либо продавать обычные товары. Посред-
ством сети можно не только продавать, но и доставлять некоторые товары. Возмож-
но, не все Web-сайты предназначены для непосредственного получения прибыли,
однако они могут обеспечивать определенную поддержку несетевых коммерческих
операций либо использоваться в качестве более дешевой рекламы.
338
Часть III. Электронная коммерция и безопасность
Типы коммерческих Web-сайтов
Как правило, коммерческие Web-сайты выполняют одну или несколько из пере-
численных ниже функций:
Распространение информации о компании путем публикации онлайновых
брошюр.
Прием заказов на поставку товаров или услуг.
Предоставление услуг или цифровой продукции.
Предоставление дополнительных товаров или услуг.
Снижение расходов.
Зачастую отдельный компонент Web-сайта может выполнять несколько из пере-
численных функций. Ниже рассматривается каждая из этих категорий и общеприня-
тые способы применения для получения прибыли или достижения каких-то иных
целей.
Данный раздел книги призван помочь читателю сформулировать цели, пресле-
дуемые при создании Web-сайта: каково его назначение и каким образом каждая его
функция будет способствовать коммерческой деятельности.
Сетевые брошюры
В начале девяностых годов прошлого века большинство Web-сайтов представляли
собой ни что иное, как сетевые брошюры или средства продаж. Сетевые брошюры и
поныне остаются одним из наиболее популярных типов коммерческих Web-сайтов,
удобным для первоначального обращения к Web или в качестве недорогого упражне-
ния в рекламе.
Брошюрным сайтом может быть все что угодно — от визуализированных в форме
Web-страниц визитных карточек до обширной коллекции маркетинговой информа-
ции. В любом случае, назначение такого сайта и смысл его существования — привле-
чение внимания клиентов к конкретной коммерческой деятельности.
Web-сайты подобного типа не приносят прямого дохода, однако способствуют
росту доходов, получаемых обычными средствами.
Разработка сайта упомянутого вида требует решения нескольких технических
проблем, впрочем, характерных и для других ситуаций. Речь идет о предотвращении
следующих просчетов:
Непредставление важной информации.
Невыразительное представление.
Отсутствие реакции на обратную связь с пользователями.
Несвоевременное обновление сайта.
Отсутствие отслеживания результатов работы.
Непредставление важной информации
Какие сведения понадобятся посетителю вашего сайта? В зависимости от того, что
посетителю уже известно, он может затребовать подробное описание товара, тем не
менее, он вполне может довольствоваться телефонами и адресами компании.
Глава 14. Эксплуатация сайта электронной коммерции
339
На многих сайтах вообще отсутствует полезная либо мало-мальски важная инфор-
мация. Как минимум, сайт должен сообщать посетителям, чем занимается данная ком-
пания, какие географические регионы она обслуживает и как с ней можно связаться.
Невыразительное представление
“В Internet никто не увидит, что ты собака” — гласит старая поговорка1. Точно так
же, как мелкие организации (то бишь “собаки”) могут выглядеть в Internet весьма
представительно, часто на невыразительных Web-сайтах даже самые крупные компа-
нии выглядят мелкими и непрофессиональными.
Web-сайт любой компании должен соответствовать самым высоким стандартам.
Текст должен быть написан и вычитан профессионалами, прекрасно владеющими
языком, а графика должна быть отчетливой, выразительной и быстро загружаемой.
Вообще говоря, на коммерческом сайте следует очень тщательно относиться к выбо-
ру графики и цветов, чтобы они соответствовали тому образу компании, который
планируется создать. Особая осторожность требуется в выборе анимации и звука;
никогда не воспроизводите анимацию и не проигрывайте звуковые клипы без явного
их затребования посетителем.
Конечно, страницы сайта не могут выглядеть совершенно одинаково на любых
машинах, во всех операционных системах и браузерах; тем не менее, они должны
быть доступными для подавляющего большинства пользователей и выводиться на
экран без ошибок.
Отсутствие реакции на обратную связь с пользователями
В Web, как и в реальном мире, хорошее обслуживание важно ничуть не меньше,
чем привлечение и удержание клиентов. Многие компании — как мелкие, так и круп-
ные — грешат тем, что не отвечают своевременно и по существу на запросы клиентов,
отправленные по адресам, указанным на Web-страницах. Клиент же, отправив сооб-
щение по электронной почте, рассчитывает на более скорый ответ, нежели при об-
мене традиционными почтовыми отправлениями. Поэтому, чтобы не обидеть кого-
либо невниманием, обработкой почты следует заниматься ежедневно.
Адреса электронной почты, указываемые на Web-страницах, должны принадле-
жать компании и ее подразделениям, но никак не отдельным сотрудникам. Иначе кто
будет заниматься почтой, адресованной, скажем, fred.smith@example.corr, после то-
го, как Фред уволится? У сообщений, адресованных на sales@example.com, сущест-
венно больше шансов попасть к тому, кто придет на место уволившегося сотрудника.
К том}' же такое сообщение может быть доставлено нескольким сотрудникам компа-
нии, что увеличивает вероятность быстрого ответа.
Возможно, вы уже сталкивались с поступлением множества спама по адресам, ука-
занным на Web-страницах. Обязательно учитывайте фактор спама при выборе способа
перенаправления почтовых сообщений, приходящих на упомянутые адреса. Более
удачным решением будет организация обратной связи с помощью формы, нежели пре-
доставление возможности напрямую отправлять электронную почту по набору адресов.
1 Следует признать, что “старая поговорка” об Internet не столь уж и стара. На самом деле,
это подпись под карикатурой Питера Штайнера (Peter Steiner), которая была напечатана в га-
зете The New Yorker за 5 июля 1993 года.
340
Часть III. Электронная коммерция и безопасность
Несвоевременное обновление сайта
Необходимо внимательно следить за тем, чтобы информация, представленная на
сайте, не устаревала. Содержимое страниц должно периодически обновляться, отра-
жая все организационные изменения в компании. Слишком “запутанный” сайт спосо-
бен отвадить клиентов, поскольку порождает неуверенность в корректности предос-
тавляемой информации.
Во избежание “застоя” необходимо своевременно обновлять страницы вручную
либо динамически с помощью какого-либо языка сценариев, например, РНР. В этом
случае достаточно запрограммировать в сценариях обращение к обновляемым ис-
точникам информации.
Отсутствие отслеживания результатов работы
Создание Web-сайта — только половина дела. Необходимо еще окупить затрачен-
ные усилия и финансы. В частности, если сайт принадлежит крупной компании, ее
руководство рано или поздно потребует отчета о том, каков вклад этого сайта в про-
цветание предприятия.
В маркетинговых кампаниях, проводимых традиционными способами, крупные
компании тратят десятки тысяч долларов на изучение рынка — причем как до начала
этих кампаний, так и после их завершения, чтобы иметь возможность судить об эф-
фективности проделанной работы. Этот подход вполне пригоден и для оценки Web-
проектов — по крайней мере, сравнительно крупных и обеспеченных солидным бюд-
жетом. Возможны также более простые и недорогие варианты:
Изучение журналов сервера. На Web-серверах хранится множество сведений о
выполнении запросов. Большая часть этой информации совершенно бесполез-
на, к тому же представлена в таком виде, что найти в ней что-либо осмысленное
практически невозможно. Чтобы выудить из этого изобилия хоть что-нибудь
полезное, необходим анализатор журналов. Существуют две популярных про-
граммы такого рода, распространяемые бесплатно: Analog (доступная по ад-
ресе http://www.analog.cx) и Webalizer (http://www.mrunix.net/webalizer).
Можно также воспользоваться более совершенной коммерческой програм-
мой наподобие Summary (http://summary.net). Анализаторы журналов по-
зволяют определять зависимость трафика от времени и посещаемость отдель-
ных страниц.
Мониторинг продаж. Назначение сетевых брошюр состоит в способствова-
нии росту' продаж. Чтобы оценить, насколько успешно они справляются с этой
задачей, достаточно сравнить уровень продаж до и после запуска Web-сайта.
Разумеется, полученный результат трудно точно оценить на фоне других мар-
кетинговых мероприятий.
Организация обратной связи. Об отношении пользователей к W’eb-сайту
можно узнать у самих пользователей, поместив на страницы формы обратной
связи или указав соответствующие адреса электронной почты. Обсуждение
можно стимулировать, установив для его участников небольшое поощрение —
скажем, участие в какой-нибудь лотерее.
Изучение типового пользователя. Надежную оценку сайта или даже прототи-
па можно выполнить при помощи целевых групп добровольцев. Добровольцам
Глава 14. Эксплуатация сайта электронной коммерции
341
предлагается оценить сайт, а затем их мнения и впечатления фиксируются на
основе интервью.
Исследование методом целевых групп, проводимое силами профессионалов,
способных оценить демографический и личностный срез будущего сообщества
пользователей, а также профессионально проинтервьюировать всех участников
эксперимента, может потребовать немалых средств. Расходы можно свести
практически к нулю, доверив работу непрофессионалам, но в этом случае трудно
гарантировать адекватность моделируемых условий и предстоящей реальности.
Впрочем, подключение к эксперименту компании, занимающейся исследования-
ми рынка — не единственный вариант получения достоверных результатов методом
целевых групп. Можно организовать собственные группы под руководством опытно-
го координатора, умеющего работать с людьми и свободного от предубеждений. В
группу следует включать от шести до десяти человек. Чтобы модератор мог полно-
стью сосредоточиться на своей работе, в помощь ему следует выделить протоколиста
или секретаря. Точность результатов будет зависеть от того, насколько удачно по-
добран состав групп. Группа, составленная из друзей или сотрудников, вряд ли ока-
жется особенно полезной.
Прием заказов на товары и услуги
Если реклама в сети организована основательно, следующий логический шаг состо-
ит в предоставлении пользователям возможности размещать заказы, не покидая сеть.
Обычным продавцам прекрасно известно, как важно побудить клиента принять реше-
ние, не откладывая. Чем больше человек размышляет, тем больше вероятность того,
что он отложит покупку или вовсе передумает. Следовательно, в интересах продавца
обслужить его как можно быстрее. Необходимость отключить модем и связаться с про-
давцом по телефону, а то и лично появиться в магазине — это серьезное препятствие на
пути к завершению сделки. Поэтому, при наличии эффективной сетевой системы рек-
ламирования, способной убедить посетителя совершить покупку, целесообразно пре-
доставить им возможность сделать это немедленно, не покидая Web-сайт.
Прием заказов через Web очень удобен для многих видов бизнеса. В конце концов,
в заказах нуждается любое предприятие, поэтому предоставление клиентам возмож-
ности делать это через сеть если и не увеличит число продаж, то, по меньшей мере,
снизит нагрузку на продавцов. Внедрение службы онлайнового заказа, естественно,
потребует дополнительных расходов, равно как и создание динамического сайта,
доступ к средствам приема платежей и обслуживание клиентов.
Основная привлекательность онлайновых продаж состоит в том, что большинст-
во затрат остаются неизменными, вне зависимости от того, сколько вы принимаете
заказов — тысячу или, скажем, миллион. Для того чтобы вернуть вложенные средства,
предлагаемые товары или услуги должны быть востребованы в разумных количест-
вах. Обязательно проанализируйте саму возможность продажи того, что вы намере-
ваетесь предложить, через сайт электронной коммерции.
Товары и услуги, продаваемые через Internet — это, главным образом, книги и
журналы, компьютеры и программное обеспечение, музыкальные записи, одежда,
туристические путевки и билеты на развлекательные мероприятия.
342
Часть III. Электронная коммерция и безопасность
Впрочем, не стоит отчаиваться, если ваш товар не относится ни к одной из на-
званных категорий — здесь уже основательно утвердились многие солидные компа-
нии. Разумнее будет рассмотреть факторы, которые поспособствовали продвижению
указанных товаров на сетевой рынок.
Товар для электронной коммерции должен быть нескоропортящимся, удобным в
доставке и достаточно дорогим, чтобы стоимость доставки казалось приемлемой. Но,
вместе с тем, он должен быть не настолько дорогим, чтобы у покупателя не возникало
желание воочию убедиться в реальности покупки прежде, чем выложить за нее деньги.
Для электронной коммерции больше подходят промышленные товары. Покупая,
скажем, авокадо, мы стараемся выбрать лучшие плоды и часто не прочь пощупать
каждый. Ведь они не все одинаковы. Что же касается книг, компакт-дисков или ком-
пьютерных программ, то здесь товары одного наименования неотличимы друг от
друга. Покупателю нет необходимости видеть каждую отдельную покупку.
Еще одно требование к товарам электронной коммерции состоит в том, что они
должны быть ориентированы на пользователей Internet. Во время написания этой
книги в данную категорию входили, в основном, трудоустроенные молодые люди,
имеющие доходы выше среднего и проживающие в крупных городах. Впрочем, со
временем в категорию пользователей Internet, скорее всего, войдет все население
планеты.
Существуют товары, которые ни разу не упоминались в отчетах по исследованию
рынка электронной коммерции, но, тем не менее, достаточно перспективные в от-
ношении упомянутого способа продаж. Например, Internet может оказаться идеаль-
ным средством продажи товара, ориентированного на определенный сегмент рынка.
Даже если в городе, где вы проживаете, только 10 человек коллекционируют вещи,
сделанные в начале восьмидесятых, сайт, продающий их, может функционировать,
если и в других городах найдется хотя бы по 10 таких коллекционеров.
Некоторые товары вряд ли могут быть пригодными для электронной коммерции.
Гаковыми являются дешевые и скоропортящиеся изделия, например, бакалея. Не-
сколько компаний все же пытались организовать их продажу через сеть, однако не
достигли особого успеха. Существуют также товары, которые удобно рекламировать с
помощью сетевых брошюр, но продавать традиционным способом. Это крупные и
дорогостоящие изделия — автомобили, недвижимость и тому подобное. Покупки по-
добного рода не делаются заочно и без тщательного взвешивания множества вариан-
тов: кроме того, возникают проблемы с доставкой (особенно это касается недвижи-
мости!).
Ниже перечислены обстоятельства, которые могут помешать сделать заказ потен-
циальном}’ покупателю:
Вопросы, оставшиеся без ответа.
Недоверие.
Неудобство в использовании.
Несовместимость.
Любое из этих обстоятельств способно помешать покупателю оформить заказ.
Глава 14. Эксплуатация сайта электронной коммерции
343
Вопросы, оставшиеся без ответа
Если потенциальный покупатель не найдет ответа на один из своих вопросов, он,
по всей видимости, покинет сайт, так и не совершив покупку. Из этого можно сделать
несколько выводов. Прежде всего, Web-сайт должен быть хорошо организован, так,
чтобы покупатель, посетивший его впервые, мог без труда найти все, что его интере-
сует. Далее, посетителю необходимо предоставить всю информацию, которая только
может потребоваться, но так, чтобы не утомить его. Пользователи Web не склонны к
вдумчивому чтению — они, скорее, предпочитают беглый просмотр. Следовательно,
тексты должны быть лаконичными. Существуют оценки максимально допустимых
объемов размещаемой информации, эмпирически определенные для каждого из
средств рекламы. В отношении Web-сайтов действуют несколько иные правила. Здесь
важны, главным образом, два параметра. Первый — стоимость сбора и обновления
информации, и второй — разумное ее размещение, структурирование и снабжение
ссылками, которые вместе позволят пользователю быстро и без труда во всем разо-
браться.
Web-сайт подобен продавцу, не требующему заработной платы и отдыха. Это, од-
нако, не освобождает от необходимости правильно организовать обслуживание кли-
ентов. Следует всячески поощрять посетителей задавать вопросы и незамедлительно
отвечать на эти вопросы по телефон}’, электронной почте, в чате или пользуясь дру-
гими средствами связи.
Доверие
С какой стати посетитель Web-сайта будет доверять представленной на нем ком-
пании, если ему совершенно не известна торговая марка этой компании? Ведь создать
сайт может кто угодно. Конечно, отсутствие доверия не может помешать чтению се-
тевой брошюры, но совсем иное дело — оформление заказа. В последнем случае кли-
енту совершенно необходимо знать, с кем он имеет дело — с заслуживающей доверия
организацией, или с уже упоминавшейся ранее “собакой”.
Совершая покупки в сети, клиенты зачастую бывают озабочены следующимим об-
стоятел ьствами:
Как будет использоваться предоставленная клиентом персональная ин-
формация? Не станет ли она доступной для посторонних? Не будет ли исполь-
зована для назойливой рекламы? Надежно ли будет храниться? Очень важно
сообщить клиенту, как будут использоваться предоставленные им сведения.
Это называется политикой конфиденциальности, и ее описание должно быть лег-
ко доступно.
Какова репутация компании, представленной на сайте? Если компания заре-
гистрирована в соответствующем учреждении, имеет физический адрес и те-
лефонный номер, существует уже в течение нескольких лет, вполне можно рас-
считывать на то, что она не окажется очередной “конторой по заготовке рогов
и копыт”, обладающей лишь несколькими Web-страницами и, как максимум,
абонентским ящиком с номером таким-то. Перечисленные выше сведения обя-
зательно должны быть представлены на страницах Web-сайта.
Что делать покупателю, недовольному покупкой? Каковы условия возврата
денег неудовлетворенному покупателю? Кто в этом случае оплачивает расходы
344
Часть III. Электронная коммерция и безопасность
по доставке? До сих пор условия возврата покупки, заказанной по почте, были
более либеральными, чем в случае приобретения непосредственно в магазине.
Очень часто гарантируется безусловный возврат. Следует оценить рост расхо-
дов на возврат покупок и сравнить его с доходами за счет дополнительных кли-
ентов, привлеченных либеральной политикой возврата. Каковой бы ни была
эта политика, ее описание необходимо разместить на страницах сайта.
Может ли клиент доверить компании сведения о своей кредитной карточ-
ке? Именно сомнения по поводу безопасности передачи этих сведений через
Internet вызывают наибольшие опасения у посетителей Web-магазинов. По-
этому необходимо не только обеспечить надежную защиту информации при
обработке кредитных карточек, но также продемонстрировать серьезное к
этому отношение. Как минимум, сведения о карточках должны передаваться из
браузера на сервер с использованием протокола SSL; на самом же сервере по-
требуется обеспечить должный уровень администрирования с соблюдением
режима секретности. Подробнее мы обсудим это в последующих главах.
Удобство использования
Клиенты различаются по уровню как компьютерной, так и общей грамотности,
говорят на разных языках, не все обладают крепкой памятью и острым зрением. От-
сюда следует, что Web-сайт должен быть по возможности простым. О проектирова-
нии пользовательского интерфейса сказано и написано немало, тем не менее, приве-
дем еще несколько полезных советов:
Web-сайт должен быть, по возможности, простым. Чем больше опций, рек-
ламы и прочих отвлекающих элементов будет представлено на каждой страни-
це, тем больше вероятность того, что пользователь во всем этом запутается.
Текст должен быть отчетливым. Не следует применять причудливые шриф-
ты. Текст не должен быть слишком мелким; необходимо учитывать зависи-
мость его размера от типа экрана.
Следует максимально упростить процесс формирования заказа. Как подска-
зывает интуиция и свидетельствует опыт, чем больше щелчков приходится де-
лать клиенту для осуществления заказа, тем меньше вероятность того, что он вы-
держит эту процедуру до конца. Необходимо свести к минимуму количество
операций. Однако при этом следует иметь в виду, что патент США2 на процесс с
использованием единственного щелчка, именуемый “1-Click”, принадлежит сайту
Amazon.com. Этот патент активно оспаривается владельцами многих Web-сайтов.
Постарайтесь не дать пользователям запутаться. Снабжайте страницы ори-
ентирами и подсказками, по которым пользователь смог бы определять свое
местоположение. Выделяйте цветом ссылки, которыми посетитель уже вос-
пользовался.
Если клиенту предоставляется виртуальная товарная тележка, в которую он “укла-
дывает” покупки, ссылка на нее должна присутствовать на экране в любой момент
времени.
2 Патент США и патент торговой палаты No 5,960,441. Метод и система размещения заказа
на приобретение товаров по сети связи.
Глава 14. Эксплуатация сайта электронной коммерции
345
Совместимость
Web-сайт обязательно должен быть протестирован в различных браузерах и раз-
ных операционных системах. Если сайт не в состоянии взаимодействовать с каким-то
популярным браузером или операционной системой, то это не только будет свиде-
тельствовать о том, что он сделан непрофессионально, но и приведет к потере по-
тенциальных клиентов.
Если сайт уже эксплуатируется, для определения типов используемых клиентами
браузеров можно прибегнуть к информации из журналов сервера. Опыт показывает,
что для персонального компьютера с операционной системой Microsoft Windows дос-
таточно тестировать Web-сайт в последних версиях браузера Microsoft Internet
Explorer и Mozilla, для Apple Mac — в последних версиях Internet Explorer и Safari, для
Linux — в последней версии Mozilla. Неплохо также протестировать сайт в текстовом
браузере наподобие Lynx. Не забывайте проверять, как выглядит ваш сайт при раз-
личных разрешениях экрана. Некоторое пользователи отдают предпочтение экранам
с высоким разрешением, однако часть пользователей получают доступ к сайту’ с по-
мощью мобильных телефонов или PDA. Очень сложно добиться, чтобы сайт одина-
ково хорошо выглядел при ширине экрана 2048 точек и при ширине 240 точек.
Новейшие функции и средства следует применять только в том случае, если пред-
полагается создание нескольких версий Web-сайта. Стандартный HTML- и XHTML-
код должен работать везде, к тому же старые возможности гораздо лучше поддержи-
ваются новыми браузерами.
Предоставление услуг и цифровых товаров
Многие товары и услуги продаются через Web, но доставляются клиенту курьером.
Небольшая их часть может быть доставлена непосредственно через сеть. Если услуга
или товар могут быть переданы через модем, их заказ, оплата и доставка происходят
незамедлительно, без вмешательства человека.
Наиболее характерный пример подобной услуги — информация. Часто она пре-
доставляется бесплатно либо оплачивается за счет рекламы, иногда — по подписке;
возможен и обычный способ оплаты — за единицу продукции.
К цифровым товарам относятся электронные книги и музыкальные записи в циф-
ровых форматах, подобных MP3 или MP4, изобразительная продукция из библио-
течных фондов, переведенная в цифровую форму, а также программное обеспече-
ние, которое также необязательно распространять только в виде записей на компакт-
дисках. Все это можно выгружать непосредственно из Internet.
Среди услуг, предоставляемых таким способом — доступ к Internet, предоставле-
ние и обслуживание Web-сайтов, а также некоторые профессиональные службы, ко-
торые могут заменять экспертные системы.
Физическая доставка товара, заказанного через Web-сайт, по сравнению с загруз-
кой цифрового продукта обладает как преимуществами, так и недостатками.
Физическая доставка требует расходов, в то время как стоимость загрузки через
Web практически равна нулю. Это означает, что расходы на доставку единицы циф-
ровой продукции мало отличаются от расходов на доставку тысячи единиц. Конечно,
это верно лишь до определенного предела, пока не потребуется установка дополни-
тельного оборудования с целью увеличения пропускной способности.
346
Часть III. Электронная коммерция и безопасность
Продажа цифровых товаров или услуг легко может быть осуществлена мгновенно.
Человек, заказавший физический товар, получит его через некоторое время — ска-
жем, в течение нескольких дней. Загрузка через Web длится несколько секунд или
минут, но именно это может привести к проблемам. Доставка приобретенного товара
электронными средствами подразумевает незамедлительность доставки. При этом не
представляется возможным контролировать процесс вручную или распределять опе-
рации с учетом суточных колебаний нагрузки. В результате системы немедленной
доставки менее защищены от компьютерных мошенников и сопряжены с большей
загрузкой ресурсов.
Цифровые товары и услуги идеально подходят для электронной коммерции, но
они составляют лишь ограниченную часть всех товаров и услуг, которые могут дос-
тавляться электронными средствами.
Дополнительные товары и услуги
Существуют области успешного коммерческого использования Web, не связанные
непосредственно с продажей товаров или услуг. Например, компании экспресс-
доставки почты UPS (www.ups.com) и Federal Express (www.fedex.com) предлагают
службы слежения, не предназначенные для непосредственного извлечения прибыли,
но дополняющие собой основные услуги. Позволяя клиентам отслеживать состояние
сделок и банковских счетов, компании получают преимущество перед конкурентами.
К этой же категории относится поддержка форумов. Создание таких групп, в ко-
торых клиенты могли бы обсуждать проблемы, возникающие при пользовании про-
дукции компании, имеет надежное экономическое обоснование. Обмен опытом по-
зволяет решать множество проблем, к тому же подобная организация технической
поддержки позволяет клиентам экономить на оплате дальней телефонной связи, и
никак не ограничена рамками рабочего времени. Что же касается компании, для нее
эти группы — наиболее дешевый способ удовлетворения претензий клиентов.
Снижение расходов
Один из наиболее популярных побудительных мотивов использования Internet —
стремление к снижению расходов. Это достигается за счет распространения инфор-
мации по сети, более оперативных и дешевых коммуникаций, замены служб и цен-
трализации.
Web-сайт — наиболее экономичное средство доставки информации большому чис-
лу людей. Прейскуранты, каталоги, документированные процедуры, спецификации и
многое другое значительно дешевле публиковать на Web-сайте, нежели печатать и
рассылать бумажные копии, особенно, в условиях частого обновления информации.
Что касается коммуникационных возможностей Internet, то ими можно воспользо-
ваться для распределения тендеров с быстрым получением результатов, для связи
клиентов с оптовыми продавцами или изготовителями (минуя посредников) и так
далее. В любом случае результат будет один — снижение расходов и рост прибыли.
Снижению расходов способствует также замена традиционных служб электрон-
ными. Примером довольно-таки смелого решения был сайт Egghead.com. Эта компа-
ния закрыла свою сеть компьютерных магазинов и полностью сосредоточилась на
электронной коммерции. Разумеется, организация крупного сайта электронной ком-
Глава 14. Эксплуатация сайта электронной коммерции
347
мерции стоит немалых денег, но это все же не идет ни в какое сравнение с расходами
на поддержку более 80 розничных торговых точек. Несомненно, замена существую-
щей службы также связана с риском. Как минимум, это ведет к потере клиентов, ко-
торые не используют Internet.
Новое предприятие Egghead.com не работает. Компания закрыла все физические
магазины во время бума “dot com” (повального перехода всех на электронную ком-
мерцию) в 1998 году и попала под защиту от банкротства в соответствие с главой 11
Кодекса о банкротствах США в 2001 году, во время спада интереса к электронной
коммерции. (Упомянутая глава 11 регулирует вопросы реорганизации неплатежеспо-
собных (обанкротившихся) компаний под руководством старого менеджмента
(обычно совместно с комитетом кредиторов) в попытке избежать полной ликвида-
ции компании). Кроме того, в настоящее время компания, похоже, перешла “под
крылышко” Amazon.com, что легко проверить, набрав в адресной строке браузера
Egghead.com и просмотрев предложенную информацию — прим, ред.)
Снижению расходов способствует и централизация. Если компания имеет не-
сколько офисов, расположенных на удалении друг от друга, ей приходится вносить
многочисленные платежи за аренду, нести накладные расходы, содержать многочис-
ленный штат сотрудников и обеспечивать оснащение рабочих мест. Предприятие
электронного бизнеса может располагаться в одном месте и в то же время быть дос-
тупным из любой точки земного шара.
Риски и угрозы
Любой бизнес связан с рисками, возникающими вследствие конкуренции, воров-
ства, неустойчивости общественных предпочтений, стихийных бедствий — список
можно продолжать до бесконечности. Однако, риски, связанные с электронной ком-
мерцией, имеют свои особенности и источники, среди которых:
Взломщики.
Невозможность привлечения компаньонов.
Отказы оборудования.
Сбои питания, коммуникационных линий или сети.
Зависимость от служб доставки.
Интенсивная конкуренция.
Ошибки программного обеспечения.
Изменения в политике и налогообложении.
Ограниченная пропускная способность системы.
Взломщики
Наиболее популяризованная угроза электронной коммерции исходит от компью-
терных злоумышленников — взломщиков. Любое предприятие подвержено угрозе на-
падения преступников, крупные же предприятия электронной коммерции привлека-
ют внимание компьютерных взломщиков разного уровня квалификации.
348
Часть III. Электронная коммерция и безопасность
Причины этого внимания различны. В одних случаях это просто “чисто спортив-
ный” интерес, в других — жажда славы Герострата, желание навредить, попытки хи-
щения денег либо бесплатного приобретения товаров и услуг.
Безопасность сайта обеспечивается сочетанием следующих мер:
Резервное копирование важной информации.
Кадровая политика, позволяющая привлекать к работе только добросовестных
людей и стимулировать добросовестность персонала. Наиболее опасны попыт-
ки взлома, исходящие изнутри компании.
Использование программного обеспечения с возможностями защиты данных и
своевременное его обновление.
Обучение персонала идентификации целей и распознаванию слабых мест сис-
темы.
Аудит и ведение журналов с целью обнаружения успешных и неудачных попы-
ток взлома.
Как правило, взлом удается по причине легко угадываемого пароля, распростра-
ненных ошибок в конфигурации и несвоевременного обновления версий программ-
ного обеспечения. Для защиты от не слишком изощренного взломщика достаточно
принятия относительно простых мер; на крайний случай, всегда должна иметься ре-
зервная копия критичных данных.
Невозможность привлечения компаньонов
Хотя атаки взломщиков вызывают наибольшие опасения, однако большинство не-
удач в области электронной коммерции все же связано с традиционными экономиче-
скими факторами. Создание и маркетинг крупного сайта электронной коммерции
требуют немалых средств. Компании предпочитают краткосрочные инвестиции,
предполагая немедленный рост числа клиентов и доходов после утверждения торго-
вой марки на рынке.
Крах электронной коммерции привел к разорению множества компаний, которые
специализировались только на ней. В цепочке крупных провалов стоит упомянуть ев-
ропейский филиал boo.com, сменивший владельца после потери за полгода почти 120
миллионов долларов США. При этом причина неудачи заключалась не в недостаточ-
ном объеме продаж — просто компания тратила намного больше, чем зарабатывала.
Отказы оборудования
Совершенно очевидно, что отказ важной части одного из компьютеров компании,
деятельность которой сосредоточена в Web, может нанести ей существенный ущерб.
Защита от простоя сайтов, работающих под высокой нагрузкой или выполняющих
важные функции, обеспечивается дублированием, благодаря чему выход из строя лю-
бого компонента не сказывается на функционировании всей системы. Однако и здесь
необходимо оценить потери от возможных простоев в сравнении с расходами на
приобретение дополнительного оборудования.
Множество компьютеров, на которых выполняются Apache, РНР и MySQL, отно-
сительно просты в настройке; кроме того, механизм репликации MySQL, позволяет
Глава 14. Эксплуатация сайта электронной коммерции
349
выполнять общую синхронизацию информации в базах данных. Тем не менее, боль-
шое число компьютеров означает и большие затраты на поддержание оборудования,
сетевой инфраструктуры и хостинга.
Сбои питания, коммуникационных линий,
сети и службы доставки
Зависимость от Internet означает зависимость от множества взаимосвязанных по-
ставщиков услуг, поэтому, если связь с остальным миром вдруг обрывается, не оста-
ется ничего иного, как только ждать ее восстановления. Это же относится к перебоям
в электропитании и забастовками или иным перебоям в работе компании, занимаю-
щейся доставкой.
Располагая достаточным бюджетом, можно иметь дело с несколькими поставщи-
ками услуг. Это влечет дополнительные расходы, однако обеспечивает бесперебой-
ность работы в условиях отказа одного из них. От кратких перебоев в электропита-
нии можно защититься установкой источников бесперебойного питания.
Интенсивная конкуренция
В случае открытия киоска на улице оценка конкурентной среды не составляет
особого труда — конкурентами будут все, кто торгует тем же товаром в пределах ви-
димости. В случае электронной коммерции ситуация несколько сложнее.
В зависимости от расходов на доставку, и также учитывая колебания курсов валют
и различий в стоимости рабочей силы, конкуренты могут располагаться где угодно.
Internet — в высшей степени конкурентная и активно развивающаяся среда. В попу-
лярных отраслях бизнеса новые конкуренты возникают почти ежедневно.
Риск, связанный с конкуренцией, с трудом поддается оценке. Здесь наиболее вер-
ная стратегия — поддержка современного уровня технологии.
Ошибки программного обеспечения
Когда коммерческая деятельность зависит от программного обеспечения, она уяз-
вима к ошибкам в этом программном обеспечении.
Вероятность критических сбоев можно свести к минимуму за счет установки на-
дежного программного обеспечения, тщательного тестирования после каждого слу-
чая замены неисправного оборудования и применения формальных процедур тес-
тирования. Очень важно сопровождать тщательным тестированием любые ново-
введения в систему.
Для уменьшения вреда, наносимого сбоями программного обеспечения, следует
своевременно создавать резервные копии всех данных; при внесении каких-либо из-
менений необходимо сохранять прежние конфигурации программ; для быстрого об-
наружения возможных неисправностей требуется вести постоянный мониторинг
системы.
Изменения в политике и налогообложении
Во многих странах деятельность в сфере электронного бизнеса не определена
(либо недостаточно определена) законодательно. Однако такое положение не может
350
Часть III. Электронная коммерция и безопасность
сохраняться вечно, и урегулирование вопроса приведет к возникновению ряда про-
блем, способных повлечь закрытие некоторых предприятий. К тому же всегда суще-
ствует опасность повышения налогов.
Этих проблем избежать невозможно. В этой ситуации единственной разумной ли-
нией поведения будет внимательное отслеживание ситуации и приведение деятель-
ности предприятия в соответствие с законодательством. Следует также изучить воз-
можность лоббирования собственных интересов.
Ограниченная пропускная способность системы
На этапе проектирования системы обязательно следует предусмотреть возмож-
ность ее роста. Успех неразрывно связан с нагрузками, поэтому система должна до-
пускать наращивание оборудования.
Ограниченного роста производительности можно достичь заменой оборудования,
однако скорость даже самого совершенного компьютера имеет предел, поэтому в
программном обеспечении должна быть предусмотрена возможность при достиже-
нии указанного предела распределять нагрузку по нескольким системам. Например,
система управления базами данных должна обеспечивать одновременную обработку
запросов от нескольких машин.
Наращивание любой системы не проходит безболезненно, однако своевременное
его планирование на этапе разработки позволяет предвидеть многие неприятности,
связанные с увеличением количества клиентов, и заранее их предупреждать.
Выбор стратегии
Многие полагают, что бурный рост Internet исключает возможность эффективно-
го планирования. Мы придерживаемся противоположной точки зрения: планирова-
ние крайне необходимо именно ввиду изменчивости Internet. Определение целей и
выбор стратегии позволяет предвидеть будущие изменения и реагировать на них с
опережением.
Итак, мы разобрались с целями создания коммерческого Web-сайта и некоторыми
из основных грозящих ему опасностей, и теперь в состоянии разработать собствен-
ную стратегию.
Стратегия должна отражать модель коммерческой деятельности. Зачастую мо-
дель — это нечто давно известное, но воспринимаемое нами как свежая и гениальная
идея. Воспользоваться ли идеями, уже апробированными в Web, либо двигаться соб-
ственным путем — каждый должен решать для себя сам.
Что дальше
В следующей главе мы рассмотрим вопросы безопасности электронной коммер-
ции — терминологию, потенциальные угрозы и методы защиты.
Глава 14. Эксплуатация сайта электронной коммерции
351
15
Безопасность сайта
электронной
коммерции
В этой главе обсуждается роль безопасности в электронной коммерции. Мы обсу-
дим, кто заинтересован в получении вашей информации, как ее можно попы-
таться получить, принципы создания стратегии, которая позволит избежать проблем
подобного рода, а также некоторые технологии для гарантированной защиты Web-
сайта, в числе которых шифрование, аутентификация и отслеживание.
В главе, помимо прочих, рассматриваются следующие темы:
Важность вашей информации.
Угрозы безопасности.
Разработка стратегии защиты.
Простота использования, производительность и безопасность.
Принципы аутентификации.
Использование аутентификации на вашем сайте.
Основы шифрования.
Шифрование с закрытым ключом.
Шифрование с открытым ключом.
Цифровые подписи.
Цифровые сертификаты.
Защищенные Web-серверы.
Аудит и регистрация.
Брандмауэры.
Резервное копирование данных.
Физическая защита.
Важность вашей информации
При рассмотрении вопросов безопасности первое, что потребуется оценить — это
важность того, что вы пытаетесь защитить. Необходимо принимать во внимание две
стороны — важность для вас и важность для потенциальных взломщиков.
Очень соблазнительно думать, что наивысшая степень защиты необходима всегда
и везде для всех сайтов, тем не менее, безопасность имеет свою цену. Перед тем как
вы решите, какие расходы и усилия оправдывают ваши средства защиты, неплохо
было бы определить стоимость вашей информации.
Очевидно, что ценность информации на компьютерах пользователя-любителя,
банка и военной организации существенно различается. Точно так же есть различия,
насколько далеко может зайти атакующий для получения доступа к этой информации.
Насколько притягательно для взломщика содержимое вашего компьютера?
Вероятно, пользователи-любители располагают весьма ограниченным временем,
чтобы изучить защиту или поработать над защитой собственных систем. Учитывая,
что на компьютерах любителей, как правило, хранится информация, ценность кото-
рой невелика для всех, за исключением владельца, то атаки на такие системы будут
нечастыми и ограниченными в плане усилий. Однако пользователи подключенных к
сети компьютеров должны предпринять значительные меры предосторожности. Да-
же если данные на компьютере не представляют никакого интереса, этот компьютер
может быть использован в качестве анонимной стартовой площадки для атак на дру-
гие системы.
Военные компьютеры являются очевидной целью для атак, как со стороны инди-
видуальных пользователей, так и иностранных правительств. Поскольку атакующие
правительства могут обладать внушительными ресурсами, благоразумно направить
дополнительные инвестиции в персонал и другие ресурсы, дабы иметь гарантию, что
в данном домене предприняты все меры предосторожности.
Привлекательность сайта электронной коммерции для взломщиков находится
приблизительно посередине между двумя описанными выше крайностями.
Угрозы безопасности
Что подвержено рискам на вашем сайте? Какие существуют внешние угрозы?
Некоторые из угроз бизнесу в сфере электронной коммерции обсуждались в гла-
ве 14. Многие из этих угроз связаны с безопасностью.
В зависимости от особенностей Web-сайта, к угрозам его безопасности могут от-
носиться:
Вскрытие конфиденциальных данных.
Потеря или уничтожение данных.
Изменение данных.
Отказ в обслуживании.
Ошибки программного обеспечения.
Отказ от обязательств.
Давайте рассмотрим каждую из этих угроз.
Глава 15. Безопасность сайта электронной коммерции
353
Вскрытие конфиденциальных данных
Данные, хранимые на вашем компьютере или передаваемые ему или с него, могут
быть конфиденциальными. К ним относится информация, которую разрешено про-
сматривать только определенным людям, например, списки оптовых цен. Это может
быть конфиденциальная информация, предоставленная клиентом, например, па-
роль, контактная информация или номер кредитной карточки.
Надеемся, что вы не храните на Web-сервере информацию, которая не предна-
значена для просмотра всеми посетителями сайта. Web-сервер — неподходящее место
для секретной информации. Если вы собираетесь сохранить на компьютере платеж-
ную ведомость или план захвата власти над всем миром, то благоразумнее не исполь-
зовать для этого Web-сервер. Web-сервер по определению является общедоступным
компьютером и должен содержать либо информацию, которую следует предоставить
клиентам, либо информацию, только что собранную от клиентов.
Для снижения риска раскрытия данных потребуется ограничить число методов
получения доступа к информации и количество людей, которые могут получить такой
доступ. Этого можно добиться, если во время проектирования постоянно помнить о
защите, должным образом конфигурировать сервер и серверные приложения, осто-
рожно программировать, тщательно тестировать, удалять ненужные службы из Web-
сервера и требовать от посетителей аутентификации.
Следует тщательно проектировать, настраивать, кодировать и тестировать, чтобы
снизить риск успешной незаконной атаки, и, что не менее важно, уменьшить вероят-
ность того, что ошибка сделает информацию доступной для случайного прочтения.
Для уменьшения количества потенциально слабых мест удалите ненужные службы
с Web-сервера. Каждая служба, запущенная на сервере, обладает своими уязвимыми
местами. Во избежание присутствия на сервере известных всем уязвимых мест следу-
ет регулярно обновлять каждую из служб. Службы, которые не используются, могут
представлять еще большую опасность. Если вы никогда не используете команду г ср.
то зачем устанавливать эту службу?1 Если в процессе инсталляции будет указано, что
компьютер подключен к сети, то программа установки, входящая в состав основных
дистрибутивов Linux и Windows NT, скопирует на ваш компьютер массу ненужных
служб, которые должны быть удалены.
Аутентификация (authentication) означает требование подтверждения своей лич-
ности посетителями. Если система знает, от кого поступает запрос, она может ре-
шить, разрешен ли доступ данному лицу. Существует несколько возможных методов
аутентификации, но в большинстве случаев применяются только пароли и цифровые
подписи. Более подробно эти методы обсуждаются ниже.
Компания CD Universe может послужить хорошим примером того, во что обхо-
дится вскрытие конфиденциальной информации, как в денежном выражении, так и в
плане репутации. Как сообщалось, в конце 1999 года взломщик, называющий себя
Maxus, связался с компанией CD Universe, утверждая, что обладает номерами 300 000
кредитных карточек, которые были украдены с сайта компании. За уничтожение но-
меров кредитных карточек он требовал выкуп в размере 100 000 долларов США.
Компания отказалась платить и оказалась втянутой в неприятную историю, попав-
1 Даже если вы используете гер в данный момент, скорее всего, имеет смысл ее удалить и
пользоваться вместо нее утилитой sep (“secure copy’’ — защищенное копирование).
354
Часть III. Электронная коммерция и безопасность
шло на первые полосы большинства газет, поскольку упомянутый Maxus раздал но-
мера мелкими партиями другим людям для нелегального использования.
Данные подвергаются риску раскрытия также и во время прохождения по сети.
Хотя сети на базе протокола TCP/IP обладают массой положительных качеств, кото-
рые сделали их стандартом де-факто для объединения различных сетей в Internet,
обеспечение безопасности не входит в их число. Протокол TCP/IP разбивает данные
на пакеты и пересылает эти пакеты от одного компьютера к другому, пока они не
достигнут пункта назначения. Это означает, что по пути следования данные проходят
через множество компьютеров, как показано на рис. 15.1. Любой из этих компьюте-
ров имеет возможность просмотреть данные в момент прохождения через него.
Internet
Рис. 15.1. Передача информации через Internet связана с ее пересыл-
кой через ряд хостов, потенциально не заслуживающих доверия
Чтобы увидеть путь, по которому проходят данные от вашего компьютера до
пункта назначения, можно воспользоваться командой traceroute на UNIX-
компьютере или tracert на Windows-компьютере. Эта команда выдает адреса компь-
ютеров, через которые проходят данные на пути к хосту назначения. Для достижения
хоста в пределах одной страны данные обычно должны пройти через добрый десяток
разных компьютеров. Для передачи данных на хосты, находящиеся в других странах,
может потребоваться более 20 промежуточных хостов. Если организация владеет
большой сложной сетью, данные могут пройти через пять хостов еще до того, как
покинут пределы здания.
Для защиты конфиденциальной информации данные можно шифровать перед
отправкой и расшифровывать после получения. Web-серверы часто используют про-
токол защищенных сокетов (Secure Socket Layer — SSL), разработанный компанией
Netscape, тем самым обеспечивая безопасное взаимодействие для данных, путешест-
вующих между Web-сервером и браузерами. Это достаточно дешевый и не требующий
больших усилий способ защиты канала передачи, но поскольку вместо простой от-
правки и приема данных сервер должен еще их зашифровывать и расшифровывать,
то количество посетителей, которых сервер может обслуживать в секунду, сущест-
венно снижается.
Глава 15. Безопасность сайта электронной коммерции
355
Потеря или разрушение данных
Потеря данных может обходиться значительно дороже, чем их вскрытие. Если на
разработку сайта и сбор данных и заказов от пользователей были затрачены месяцы
работы, то во что может обойтись потеря этой информации в денежном выражении,
в смысле испорченной репутации и напрасно потраченного времени? Если у вас не
было резервных копий данных, придется спешно переписывать сайт и начинать все с
самого начала. Вы можете столкнуться с массой недовольных клиентов, а также и
мошенников, которые будут утверждать, что заказывали товары, но не получили их.
Вполне возможно, взломщикам все же удастся проникнуть в систему' и отформати-
ровать жесткий диск. Существует высокая вероятность того, что небрежный про-
граммист или администратор случайно удалит что-то, и почти не вызывает сомнений,
что рано или поздно жесткий диск потерпит аварию. Жесткие диски вращаются со
скоростью в несколько тысяч оборотов в минуту, и временами они отказывают. Со-
гласно закону Мерфи, отказавший диск будет содержать наиболее важные данные,
для которых дольше всего не выполнялось резервное копирование.
Во избежание потери данных можно предпринять различные меры. Защищайте
свои серверы от взломщиков. Стремитесь к тому', чтобы доступ к компьютеру имело
как можно меньше людей. Нанимайте только компетентных и добросовестных со-
трудников. Приобретайте только качественные жесткие диски. Используйте RAID-
массивы (Redundant Array of Inexpensive Disks — массив недорогих дисков с избыточ-
ностью), которые содержат несколько дисков. Такие массивы могут работать как
один высокопроизводительный и надежный диск.
Независимо от причин, существует только один реальный способ защиты от по-
тери данных — резервное копирование. Резервное копирование — это не высшая ма-
тематика. Наоборот, это утомительный, скучный и, хочется надеяться, бесполезный
процесс, однако он жизненно необходим. Удостоверьтесь, что данные регулярно ко-
пируются на резервные носители, и протестируйте процедуру резервного копирова-
ния, чтобы иметь уверенность в возможности восстановления данных. Убедитесь
также, что резервные копии хранятся вдалеке от ваших компьютеров. Хотя и мало-
вероятно, что офис пострадает от пожара или еще какого-нибудь стихийного бедст-
вия, хранение резервных копий вне офиса — это достаточно дешевая, тем не менее,
надежная страховка.
Изменение данных
Хотя потеря данных может принести большой ущерб, не меньший, а то и больший
ущерб причиняет изменение данных. Что произойдет, если кто-то получит доступ к
системе и изменит файлы? Хотя полное удаление может быть замечено и исправлено
с использованием резервной копии, насколько скоро удастся заметить несанкциони-
рованное изменение?
Изменению могут подвергнуться данные и выполняемые файлы. Изменение фай-
лов данных может быть обусловлено желанием взломщика “украсить" ваш сайт собст-
венным граффити или получить незаконные привилегии. Замена выполняемых фай-
лов их вредительскими версиями может открыть взломщику’, полечившему доступ к
сайту, своего рода “черный ход” для последующих визитов и получения больших пол-
номочий в системе.
356
Часть III. Электронная коммерция и безопасность
Данные, которые передаются через сеть, можно защитить с помощью цифровой
подписи. Это не помешает злоумышленникам изменить данные, но если получатель
имеет возможность проверить соответствие подписи при получении файла, он будет
знать, был ли файл изменен. Если во избежание несанкционированного просмотра
данные шифруются, это также существенно усложняет скрытую модификацию дан-
ных на пути их следования.
Защита от изменений в файлах, хранящихся на сервере, требует применения пре-
доставленных операционной системой возможностей контроля доступа к файлам.
Воспользуйтесь этими возможностями для защиты системы от несанкционированно-
го доступа. Используя механизм прав доступа к файлам, посетителям можно разре-
шить использовать систему, но не предоставлять им полный контроль над изменени-
ем системных файлов и файлов других пользователей. Отсутствие соответствующих
систем прав доступа — это одна из причин, по которым Windows 98, Windows 95 и
Windows ME не подходят в качестве операционных систем для серверов.
Обнаружение изменений является достаточно сложной задачей. Если в какой-то
момент становится понятно, что защита системы взломана, то каким образом выяс-
нить, изменились ли важные файлы? Некоторые файлы, такие как файлы, хранящие-
ся в базах данных, должны со временем изменяться. Множество других файлов долж-
ны оставаться неизменными с момента установки. Такие файлы меняются только в
случае их сознательного обновления. Модификация файлов — как программ, так и
данных — может осуществляться весьма коварно, и хотя программы можно устано-
вить повторно, вы не сможете определить, какая из версий данных была “чистой”.
Программное обеспечение определения целостности файлов наподобие Tripwire
фиксирует информацию о важных файлах, которые находятся в известном безопас-
ном состоянии, возможно, немедленно после установки. Впоследствии эта информа-
ция может быть использована для проверки целостности файлов. Коммерческую и
условно-бесплатную версию этого приложения можно загрузить из сайта по следую-
щему адресу:http://www.tripwire.com.
Отказ в обслуживании
Одна из наиболее сложных с точки зрения защиты угроз — это угроза отказа в об-
служивании (Denial of Service — DoS). Эта угроза появляется, когда чьи-то действия
приводят к затруднению или невозможности для пользователей получить доступ к
определенной службе. Кроме того, отказом в обслуживании считается задержка при
доступе к службе, критичной с точки зрения времени.
В начале 2000 года произошло большое количество атак типа распределенного отка-
за в обслуживании (Distributed Denial of Service — DDoS) на известные и поггулярные
Web-сайты. В список подвергнувшихся атакам сайтов вошли Yahoo!, eBay, Amazon. Е-
Trade и Buy.com. Упомянутые сайты приспособлены к потокам данных такого объе-
ма, о котором многие из нас могут только мечтать. И все-таки эти сайты уязвимы для
DoS-атак и были недоступны в течение нескольких часов. Хотя взломщики мало полу-
чают от полного отключения сайта, владелец сайта может терять деньги, время и,
самое главное — репутацию.
Для некоторых сайтов характерны периоды времени, когда выполняется основ-
ная работа. Букмекерские конторы, работающие в Internet, имеют дело с большим
числом ставок перед крупным спортивным (или другим) событием. Один из спосо-
Глава 15. Безопасность сайта электронной коммерции
357
бов, когда взломщики попытались получить выгоду из DDoS-атак в 2004 году, был свя-
зан с вымогательством денег у букмекерских контор под угрозой атаки их сайтов в
период максимальной загрузки.
Одна из причин, почему от атак подобного типа очень сложно защищаться, связа-
на с многообразием применяемых методов. В число этих методов входит установка на
целевом компьютере программы, которая будет использовать почти все процессор-
ное время, обратную массовую рассылку либо применение одного из автоматизирован-
ных инструментов. Метод обратной массовой рассылки состоит в том, что кто-то
инициирует поддельную массовую рассылку, или спам (spam), в которой в качестве от-
правителя указан атакуемый узел. В результате сайт получи! тысячи сердитых отве-
тов. которые еще и потребуется обработать.
Существуют автоматизированные средства, позволяющие запустить распределен-
ную DoS-атаку на выбранный сервер. Даже не обладающий большими знаниями чело-
век может исследовать несколько компьютеров на предмет известных уязвимых мест,
получить доступ к какому-то из компьютеров и установить эту утилиту’. Поскольку
весь процесс является полностью автоматическим, атакующий может установить та-
кую программу на узле меньше чем за пять секунд. Когда утилита установлена на дос-
таточном количестве узлов, на все такие узлы передается команда “утопить” атакуе-
мую цель сетевым трафиком.
В общем случае защищаться от DoS-атак достаточно сложно. Однако, проведя не-
большие исследования, вы узнаете и закроете порты, используемые по умолчанию
большинством утилит для DDoS-атак. Ваш маршрутизатор может предоставлять ме-
ханизмы, такие как ограничение процентной доли трафика, использующего опреде-
ленный протокол, например, ICMP. Значительно проще обнаружить хосты сети, ко-
торые используются для атак на другие хосты, нежели защищать от атак собственный
хост. Если бы каждый сетевой администратор смог сосредоточиться исключительно
на наблюдении за собственной сетью, проблема DDoS-атак стояла бы не столь остро.
В связи с существованием очень большого количества возможных методов атак,
единственный эффективный метод защиты — наблюдение за нормальным трафиком
в сети и наличие группы экспертов, что позволит предпринять соответствующе
контрмеры при появлении каких-либо отклонений.
Ошибки программного обеспечения
Существует ненулевая вероятность присутствия ошибок в приобретенном, полу-
ченном или разработанном самостоятельно программном обеспечении. Учитывая,
что для разработки Web-проектов отводится очень мало времени, наличие ошибок в
программном обеспечении весьма вероятно. Любая основанная на применении ком-
пьютеров коммерческая деятельность предельно чувствительна к ошибкам про-
граммного обеспечения.
Ошибки в программах могут приводить к любым видам непредсказуемого поведе-
ния, в том числе к недоступности служб, брешей в защите, финансовым потерям и
просто плохому обслуживанию клиентов.
Типичными причинами ошибок могут быть неполные спецификации, ошибочные
предположения, сделанные разработчиками, и неадекватное тестирование.
358
Часть III. Электронная коммерция и безопасность
Неполные спецификации
Чем более пестрой или неоднозначной будет документация по вашему проекту,
гем выше вероятность наличия ошибок в конечном продукте. И хотя может пока-
заться излишним указывать в спецификации, что если кредитная карточка клиента
отклонена, заказ не должен ему отсылаться, по крайней мере, один крупнобюджет-
ный сайт содержал такую ошибку. Чем меньше у разработчиков опыта в создании
систем, подобных разрабатываемой в данный момент, тем более точными должны
быть спецификации.
Предположения, сделанные разработчиками
Проектировщики и программисты системы делают много предположений. Очень
хочется надеяться, что все свои предположения они отразят в документации, и при
этом окажутся правы. Тем не менее, иногда люди ошибаются в своих предположени-
ях. Таковыми могут быть предположения, что введенные данные будут корректными,
не будут содержать необычных символов или размер данных не превысит определен-
ного значения. Неверными могут быть также предположения о синхронизации во
времени, такие как вероятность выполнения двух конфликтующих действий в одно и
то же время, или предположение о том, что выполнение сложной обработки займет
больше времени, чем решение простой задачи.
На подобные предположения легко не обратить внимания, поскольку обычно они
верны. Взломщик может воспользоваться переполнением буфера, если программист
сделал предположение о длине входных данных, или обычный пользователь может
запутаться и покинуть сайт из-за того, что программист не учел, что в имени пользо-
вателя можег встречаться символ апострофа. Ошибки подобного рода можно оты-
скать и исправить в ходе тщательного тестирования и детального анализа кода.
Исторически сложилось так, что используемые взломщиками бреши в приложе-
ниях и операционных системах обычно связаны с переполнением буфера или усло-
виями состязаний.
Некачественное тестирование
Практически невозможно протестировать все условия ввода данных на всех воз-
можных типах оборудования и операционных системах со всеми возможными поль-
зовательскими настройками. Это еще более справедливо по отношению к системам,
работающим в среде Web.
На самом деле просто требуется хороший план тестирования, который обеспе-
чит проверку' всех функций приложения на представительском наборе распростра-
ненных типов компьютеров. Цель правильно спланированного набора тестов
должна состоять в том, чтобы, как минимум, один раз протестировать каждую стро-
ку' кода созданного приложения. В идеале этот набор тестов должен быть автомати-
зированным, чтобы его без особых усилий можно было запускать на выбранных
компьютерах.
Наибольшая проблема тестирования заключается в том, что оно мало увлекатель-
но, к тому же должно выполняться многократно. И хотя некоторым людям нравится
ломать вещи, очень немногим понравится ломать одну' и ту' же вещь снова и снова.
Исключительно важно привлекать к тестированию не только разработчиков, но и
посторонних людей. Одна из основных целей тестирования — выявить ошибочные
Глава 15. Безопасность сайта электронной коммерции
359
предположения, сделанные разработчиками. Скорее всего, новый человек будет ис-
ходить из совершенно иных предпосылок. Кроме того, профессионалы редко прояв-
ляют энтузиазм при поиске ошибок в собственных разработках.
Отказ от обязательств
Последний риск, который мы рассмотрим, — это отказ от обязательств (repudiation).
Он имеет место, когда сторона, участвующая в транзакции, отказывается принимать
в ней участие. Примером из области электронной коммерции может послужить чело-
век, который заказал товары на Web-сайте, а затем отказался санкционировать сня-
тие денег с кредитной карточки. В качестве примера можно также привести челове-
ка, который соглашается с чем-то в сообщении электронной почты, а затем требует,
чтобы его письмо было забыто.
В идеальном случае финансовые транзакции должны обеспечивать обеим сторо-
нам душевное спокойствие в отношении отказа от обязательств. Ни один из участни-
ков не должен иметь права отказаться от своего участия в транзакции, или, точнее,
оба участника сделки должны иметь возможность последовательно и документально
подтвердить действия другого участника для третьей стороны, такой как суд. На
практике подобное происходит достаточно редко.
Аутентификация придает некоторую уверенность о тех, с кем приходится сотруд-
ничать. Будучи выдан авторитетным органом, цифровой сертификат подлинности
может обеспечить большую уверенность.
Отправляемые каждой из сторон сообщения также необходимо защищать от под-
делки. Вы немногого добьетесь, доказав, что компания Corp Pty Ltd. отправила вам
сообщение, если не удастся доказать, что полученное сообщение в точности совпада-
ет с отправленным компанией. Как упоминалось ранее, цифровые подписи и шифро-
вание существенно усложняют незаметное изменение сообщений.
Для выполнения транзакций между сторонами, поддерживающими регулярные
отношения, эффективным способом снижения количества отказов от обязательств
являются цифровые сертификаты в комбинации с обменом зашифрованными или
подписанными сообщениями. Такие методы неэффективны для одноразовых тран-
закций, например, начальной транзакции между сайтом электронной коммерции и
неизвестным посетителем, обладающим кредитной карточкой.
Для подтверждения своей добросовестности в отношении посетителей компа-
ния, занимающаяся электронной коммерцией, должна быть готова предоставить
центру сертификации, такому как VeriSign (http: //www.verisign.com) или Thawte
(http: I /www. thawte. com), доказательства подлинности и несколько сот долларов. Но
захочет ли та же компания отказать в обслуживании посетителю, который отказыва-
ется сделать то же для подтверждения своей личности? При выполнении транзакций
с небольшими суммами торговцы, в основном, готовы пойти на определенный риск
обмана или отказа от обязательств, дабы не потерять потенциальных покупателей.
360
Часть 111. Электронная коммерция и безопасность
Простота использования,
производительность и безопасность
По своей природе Web — среда, сопряженная с рисками. Она была создана для то-
го, чтобы позволить большому количеству неизвестных пользователей запрашивать
определенные службы от различных компьютеров. Большинство запросов будут
вполне легальными запросами на получение Web-страниц, но если ваш компьютер
подключен к Internet, ничто не мешает людям пытаться установить соединения дру-
гого типа.
Хотя весьма естественно предположить, что желательно обеспечить наивысший
уровень защиты, на практике это встречается не так уж часто. Если вы хотите чувст-
вовать себя в полной безопасности, держите все свои компьютеры выключенными,
отсоединенными от всех сетей и закрытыми в сейфе. Чтобы компьютеры были по-
лезными и доступными для использования, придется пойти на некоторые послабле-
ния в защите.
Между безопасностью, простотой использования, стоимостью и производитель-
ностью должен быть достигнут определенный компромисс. Если сделать службу бо-
лее защищенной, это может снизить простоту ее использования, например, если ог-
раничить клиенту' количество разрешенных действий или потребовать, чтобы клиент
идентифицировал себя. Повышение безопасности может также повлечь за собой
снижение производительности компьютеров. Использование программного обеспе-
чения для повышения безопасности системы, например, системы шифрования, сис-
темы обнаружения вторжений, антивирусных программ, системы расширенной ре-
гистрации, требует дополнительных ресурсов. Для проведения шифрованного
сеанса, такого как соединение с Web-сайтом по протоколу SSL, потребуется значи-
тельно большая вычислительная мощность, нежели для проведения обычного сеанса.
Подобного рода потери производительности можно компенсировать, потратив
больше денег на более мощные компьютеры или на оборудование, специально соз-
данное для шифрования данных.
Производительность, простота использования, затраты и безопасность можно
считать взаимоисключающими целями. Для достижения компромисса потребуется
проанализировать необходимые уступки и принять обоснованное решение. Компро-
мисс можно будет найти в зависимости от того, какова ценность вашей информации,
сколько пользователей предполагается обслуживать, и какие препятствия могут воз-
никнуть со стороны легальных посетителей вашего сайта.
Разработка стратегии безопасности
Стратегия безопасности — это документ, описывающий следующие аспекты:
Общую философию защиты в данной организации.
Элементы, которые должны быть защищены — программное обеспечение,
оборудование, данные.
Персонал, отвечающий за защиту' необходимых элементов.
Стандарты безопасности и метрики, измеряющие степень удовлетворения
этих стандартов.
Глава 15. Безопасность сайта электронной коммерции
361
Хорошим пособием при разработке стратегии безопасности может служить то,
что она подобна составлению функциональных требований для программного обес-
печения. Стратегия не должна затрагивать конкретные реализации или решения;
вместо этого она должна отражать цели защиты и требования, предъявляемые к ней в
данной среде. Стратегия безопасности не должна обновляться слишком часто.
В отдельном документе следует зафиксировать принципы того, как требования
стратегии защиты должны быть реализованы в конкретном окружении. Эти принци-
пы могут различаться для разных подразделений организации. Данный документ
представляет собой проектный документ или методическое руководство, в котором
отражено то, что на самом деле следует проделать, чтобы гарантировать заявленный
уровень защиты.
Принципы аутентификации
Аутентификация — это попытка доказать, что кто-то на самом деле является тем, за
кого себя выдает. Существует множество методов аутентификации, но, как это имеет
место со многими средствами защиты, чем большую степень безопасности обеспечи-
вают методы, тем сложнее их использовать.
К технологиям аутентификации относятся использование паролей, цифровых
подписей, биометрических показателей, таких как отпечатки пальцев, а также мето-
ды, предусматривающие применение специального оборудования, например, смарт-
карт. В Web широкое применение находят только два метода — использование паро-
лей и цифровых сертификатов.
Биометрические методы и большинство методов, предполагающих применение
специального обррудования, требуют наличия специализированных устройств ввода
и привязывают санкционированных пользователей к компьютерам, па которых уста-
новлены устройства подобного рода. Это может быть приемлемым или даже жела-
тельным для доступа к внутренним системам организации, но такой подход сводит на
нуль все преимущества систем, доступных через Web.
Пароли легко реализовать, просто использовать, и они не требуют наличия ника-
ких устройств ввода. Пароли обеспечивают некоторый уровень аутентификации, но
не подходят в качестве единственного метода аутентификации в системах с высоким
уровнем безопасности.
Концепция паролей достаточно проста. Пароль каждого пользователя известен
только самому пользователю и системе. Если посетитель утверждает, что он — это вы,
и знает ваш пароль, то у системы имеются все основания верить что посетитель — это
вы. До тех пор пока никто не знает и не может угадать ваш пароль, система остается
защищенной. Сами по себе пароли обладают несколькими потенциальными недос-
татками и не могут обеспечить надежную аутентификацию.
Множество паролей угадать несложно. Если выбор пароля возложить на пользо-
вателей, то 50% из них выберет пароль, угадать который не представляет особых
трудностей. Обычно такими паролями являются слова из словаря и имена пользова-
телей, задействованные в качестве имен их учетных записей. Ценой уменьшения
удобства использования можно заставить пользователей применять в паролях цифры
и знаки пунктуации. Однако это приведет к тому’, что у пользователей появятся труд-
ности с запоминанием паролей. Конечно, обучение пользователей правилам выбора
362
Часть III. Электронная коммерция и безопасность
хороших паролей может помочь, но даже после обучения около 25% пользователей
все равно выберут легко угадываемые пароли. Имеет смысл создать стратегию выбо-
ра паролей, которая будет препятствовать пользователям в выборе легко угадывае-
мых паролей. Для этого стратегия должна сравнивать новые пароли со словарем или
требовать использования в пароле нескольких цифр, знаков пунктуации или комби-
нации прописных и строчных букв. Оборотная сторона упомянутого подхода состоит
в том, что строгие правила выбора паролей приведут к тому, что многие пользовате-
ли попросту не сумеют запомнить собственные пароли.
Трудно запоминаемые пароли увеличивают вероятность того, что пользователь
может, например, записать на листке фразу “Пользователь: Fred Пароль: rover" и
прилепить его к монитору.
Пользователей следует учить не записывать пароли и не совершать других подоб-
ных глупых поступков, например, не сообщать пароль человеку, который позвонил
по телефону и утверждает, что работает в системе.
Пароли могут перехватываться электронным путем. Используя программы перехвата
клавиатурного ввода на терминалах либо анализаторы сетевых протоколов, или сниф-
феры (sniffers), взломщики могут перехватывать интересные пары “имя пользователя-
пароль”. Шифрование сетевого потока данных позволяет снизить риск перехвата па-
ролей.
При всех своих недостатках пароли являются простым и относительно эффективным
способом аутентификации пользователей. Они обеспечивают уровень защиты, который,
возможно, не подойдет для министерства национальной безопасности, однако идеаль-
но подходит для проверки состояния доставки товаров, заказанных клиентом.
Аутентификация
Механизмы аутентификации встроены в наи-
более популярные Web-браузеры и серверы. Web-
серверы могут затребовать указания имени пользо-
вателя и пароля у тех, кто желает выгрузить файлы
из определенных каталогов на сервере.
Когда браузер получает запрос на ввод имени
пользователя и пароля, он отображает диалоговое
окно, подобное показанному на рис. 15.2.
Web-серверы Apache и Microsoft IIS позволяют
таким способом легко защитить весь сайт или же его
часть. В случае применения РНР и MySQL существу-
ет множество способов достичь того же эффекта.
Применение MySQL обеспечивает большую произ-
водительность, нежели встроенная аутентифика-
ция. С помощью РНР можно реализовать более гиб-
кую аутентификацию или представить запрос в
более привлекательной форме.
Рис. 15.2. Web-браузер требует от
пользователей аутентификации
при попытке посещения защи-
щенного каталога на Web-сервере
Некоторые примеры аутентификации можно найти в главе 16.
Глава 15. Безопасность сайта электронной коммерции
363
Основы шифрования
Алгоритм шифрования (encrypiion algorithm) — это математический процесс преоб-
разования информации в строку данных, которые выглядят как ста чайные.
Исходные данные часто называют открытым или простым текстом (plain text), хотя
для процесса шифрования не имеет значения, что представляет собой информация —
реальный текст или данные другого рода. Аналогично, зашифрованная информация
называется зашифрованным текстом (cipher text), ио, как правило, она мало напоми-
нает текст. На рис. 15.3 процесс шифрования представлен в виде простой блок-
схемы. Открытый текст загружается в механизм шифрования, который может быть
даже механическим устройством наподобие машины Enigma, применявшейся во вре-
мена второй мировой войны. В настоящее время почти все шифровальные машины —
это компьютерные программы. Шифровальный механизм создает зашифрованный
текст.
Рис. 15.3. Процесс шифрования получает на вхо-
де открытый текст и преобразует его в зашифро-
ванный текст, который выглядит как случайный
Чтобы создать защищенный каталог, при попытке доступа к которому открывает-
ся диалоговое окно, показанное на рис. 15.2, мы воспользовались самым простым ме-
тодом аутентификации, который обеспечивает сервер Apache. (В следующей главе
применение этого метода рассматривается более подробно.) Этот метод шифрует
пароли перед их сохранением. Мы создали пользователя с паролем password. Этот
пароль был зашифрован и сохранен в виде строки aWDuA3X3H.mc2. Как видите, от-
крытый и зашифрованный текст внешне не похожи друг на друга.
Показанный метод шифрования не является обратимым. Многие пароли сохра-
няются с помощью однонаправленного алгоритма шифрования. Для проверки кор-
ректности вводимого пароля расшифровывать сохраненный пароль не потребуется.
Вместо этого вводимый пароль шифруется, и результат сравнивается с сохраненной
версией.
Многие, но не все процессы шифрования могут быть обратимыми. Обратный
процесс называют дешифрацией (decryption). На рис. 15.4 показан двунаправленный
процесс шифрования.
История криптографии насчитывает почти 4000 лет. но наибольшего развития эта
наука достигла в период второй мировой войны. С тех пор развитие криптографии
повторяет развитие компьютерных сетей — сначала криптография использовалась
только военными и финансовыми организациями, с семидесятых годов прошлого века
криптография стала шире использоваться в коммерческих компаниях, а в девяностых
годах, опять-таки прошлого века, криптография стала применяться практически по
всеместно. За последние несколько лет криптография прошла путь от концепции, с
которой обычные люди сталкивались только в фильмах о второй мировой и в шпион-
ских триллерах, до технологии, о которой каждый день можно прочесть в газетах, и
которая применяется при каждом приобретении чего-нибудь через Web.
364
Часть III. Электронная коммерция и безопасность
Рис. 15.4. В процессе шифрования открытый текст преобразуется в зашифро-
ванный текст, который выглядит как случайный. В процессе дешифрации за-
шифрованный текст преобразуется обратно в от крытый текст
Существует великое множество различных алгоритмов шифрования. Некоторые,
например, DES, используют секретный, или закрытый ключ. Другие, например, RSA,
используют открытый и отдельный закрытый ключи.
Шифрование с закрытым ключом
Шифрование с закрытым ключом (private key encryption) основано на том, что доступ
к ключу имеет только авторизованный персонал. Этот ключ должен держаться в сек-
рете. Если ключ попадет в нехорошие руки, посторонний сможет получить несанк-
ционированный доступ к зашифрованной информации. Как показано на рис. 15.4, и
отправитель (который шифрует сообщение), и получатель (который дешифрует со-
общение) владеют одним и тем же ключом.
Наиболее широко используемым алгоритмом с закрытым ключом является стан-
дарт Data Encryption Standard (DES). Этот алгоритм, разработанный компанией IBM в
семидесятых годах прошлого века, принят в качестве американского стандарта для
коммерческих и несекретных правительственных коммуникаций. Современные ско-
рости вычислений на порядок превышают скорости вычислений в семидесятых го-
дах, поэтому алгоритм DES считается устаревшим как минимум с 1998 года.
Другие известные системы шифрования с закрытым ключом — это RC2, RC4, RC5,
тройной DES (triple DES) и IDEA. Тройной DES-алгоритм обеспечивает достаточную
степень защиты2. Этот алгоритм использует тот же метод шифрования, что и DE.S, но
применяет его трижды, используя при этом до трех разных ключей. Открытый текст
шифруется с использованием первого ключа, дешифруется при помощи второго
ключа, а затем шифруется с применением третьего ключа.
Явный недостаток алгоритмов с закрытым ключом состоит в том, что для отправ-
ки кому-то защищенного сообщения необходимо располагать безопасным способом
передачи этому лицу закрытого ключа. А если у вас есть безопасный метод передачи
ключа, то почему не воспользоваться этим же методом для передачи сообщений?
К счастью, в 1976 году произошел прорыв, когда Дифи (Diffie) и Хелман (Hellman)
опубликовали первый алгоритм шифрования с открытым ключом.
2 Как ни парадоксально звучит, но тройной DES всего лишь в два раза безопаснее обычного
алгоритма DES. Если вам настоятельно необходимо реализовать алгоритм, который в три раза
безопаснее обычного алгоритма DES, то придется написать программ}', которая применяет
стандартный алгоритм DES не три. а четыре раза.
Глава 15. Безопасность сайта электронной коммерции
365
Шифрование с открытым ключом
Шифрование с открытым ключом (public key encryption) базируется на двух различ-
ных ключах — открытом и закрытом. Как показано на рис. 15.5, открытый ключ ис-
пользуется для шифрования сообщений, а закрытый — для их дешифрации.
Рис. 15.5. При шифровании с открытым ключом для шифрования и дешиф-
рации используются различные ключи
Преимущество этого подхода состоит в том, что, как следует из его названия, от-
крытый ключ можно свободно распространять. Любой человек, которому вы передали
свой открытый ключ, может отправить вам защищенное сообщение. Но поскольку за-
крытым ключом обладаете только вы, только вы и сможете дешифровать сообщение.
Наиболее известный алгоритм с открытым ключом — это алгоритм RSA, который
был разработан Ривестом (Rivest), Шамиром (Shamir) и Адельманом (Adelman) в Мичи-
ганском технологическом институте (MIT) и опубликован в 1978 году. Ранее алгоритм
RSA был защищен патентом, но срок действия патента истек в сентябре 2000 года.
Огромным преимуществом алгоритмов с открытым ключом является возмож-
ность передачи открытого ключа по незащищенному каналу, не беспокоясь, что он
будет прочитан третьей стороной. Несмотря на это, системы с закрытым ключом все
еще используются повсеместно. Часто можно встретить гибридные системы. В таких
системах алгоритм с открытым ключом применяется для передачи закрытого ключа,
который используется для обмена данными до конца сеанса связи. Эта дополнитель-
ная сложность компенсируется тем, что алгоритмы с закрытым ключом работают на
три порядка быстрее алгоритмов с открытым ключом.
Цифровые подписи
Цифровые подписи (digital signature) относятся к криптографическим алгоритмам с
открытым ключом, но с измененными ролями открытого и закрытого ключей. От-
правитель может зашифровать и подписать сообщение своим закрытым ключом. Ко-
гда сообщение получено, получатель может дешифровать его, используя открытый
ключ отправителя. Ввиду того, что отправитель — это единственное лицо, обладаю-
щее доступом к закрытому' ключу, то получатель достаточно точно знает, от кого по-
лучено сообщение, а также может быть уверен, что сообщение не было изменено.
Цифровые подписи могут оказаться весьма полезными. Они гарантируют получа-
телю, что сообщение не было подделано, а также не позволяют отправителю отка-
заться от обязательств, отрицая факт отправки сообщения.
366
Часть III. Электронная коммерция и безопасность
Важно заметить, что хотя сообщения шифруются, их может прочитать любой об-
ладатель открытого ключа. Несмотря на то что используются те же методы и ключи,
в данном случае назначением шифрования является не запретить чтение, а предот-
вратить подделку и отказ от обязательств.
Поскольку алгоритмы с открытым ключом работают достаточно медленно с
большими сообщениями, для повышения производительности обычно используется
алгоритм другого типа, называемый хеш-функцией (hash function).
Хеш-функция вычисляет дайджест, или хеш-значение, для каждого указанного со-
общения. Совершенно не важно, какое значение генерирует алгоритм. Важно, что
результат этой функции является детерминированным, то есть результат будет одним
и тем же каждый раз, когда на вход передаются одни и те же данные. Кроме того,
важно, что результат имеет небольшой размер, и алгоритм быстро работает.
Наиболее известные хеш-функции — это MD5 и SHA.
Хеш-функция генерирует дайджест, соответствующий определенному сообщению.
Располагая сообщением и его дайджестом, можно убедиться, не подделывалось ли
сообщение, но только в том случае, если дайджест не был подделан вместе с ним.
И, наконец, обычный способ создания цифровой подписи — это создание с помо-
щью быстрой хеш-функции дайджеста для всего сообщения, а затем шифрование
только короткого дайджеста с использованием медленного алгоритма с открытым
ключом. Теперь подпись можно отправить вместе с сообщением по любому обычно-
му, незащищенному каналу связи.
После получения можно проверить подлинность подписанного сообщения. Под-
пись дешифруется с помощью открытого ключа отправителя. Хеш-значение для со-
общения генерируется с помощью того же метода, который использовал отправи-
тель. Если дешифрованное хеш-значение совпадает со сгенерированным значением,
значит, сообщение действительно прислано отправителем и при пересылке не изме-
нялось.
Цифровые сертификаты
Хорошо бы иметь возможность проверять, что сообщение не было изменено, а
вся последовательность сообщений поступила от определенного компьютера или
пользователя. Для коммерческих взаимодействий еще лучше было бы иметь возмож-
ность связать пользователя или сервер с каким-либо реальным правовым понятием,
таким как физическое или юридическое лицо.
Цифровой сертификат объединяет в цифровой подписанной форме открытый
ключ и подробную информацию о человеке или организации. Получив сертификат,
вы получаете открытый ключ другой стороны для отправки ей, при необходимости,
зашифрованных сообщений. Кроме того, цифровой сертификат содержит подроб-
ную информацию о другой стороне, которая заведомо не подвергалась изменениям.
В данном случае проблема состоит в том, что информация из сертификата заслу-
живает ровно столько доверия, сколько и подписавший его человек. Любой человек
может создать и подписать сертификат, в котором будет утверждаться все, что угод-
но. Для коммерческих транзакций полезно наличие третьей, заслуживающей доверия
стороны, которая будет проверять подлинность участников и сведений, записанной в
их сертификатах.
Глава 15. Безопасность сайта электронной коммерции
367
Такие третьи стороны называют центрами сертификации (Certifying Authority —
С.А). Центры сертификации выдают цифровые сертификаты отдельным лицам и ком-
паниям, которые должны для этого пройти проверку на подлинность.
Из всех центров сертификации наиболее известными являются VeriSign
(http://www.verisign.com/) и Thawte (http: I/www.thawte.com/). VeriSign и Thawte
являются собственностью одной компании, поэтому существенной разницы между их
сертификатами нет. Сертификаты некоторых из менее известных центров сертифи-
кации, таких как Equifax Secure (www. equif axsecure. com) стоят значительно дешевле.
Центры сертификации подписывают сертификаты, подтверждая, что им были
представлены доказательства подлинности лица или компании. Важно заметить, что
сертификат не является справкой или официальным подтверждением платежеспо-
собности. Он не гарантирует, что вы имеете дело с кем-то, обладающим хорошей ре-
путацией. Сертификат гарантирует то, что если вас ограбят, то у вас будет шанс най-
ти реальный физический адрес того, кого можно будет привлечь к суду.
Сертификаты позволяют создать сеть доверия. Предположим, вы решили дове-
рять центру сертификации; тогда вы можете решить доверять людям и компаниям,
которым доверяет выбранный центр сертификации. Далее можно решить доверять
всем лицам и организациям, которым доверяет владелец сертификата.
Цифровые сертификаты чаще всего используются с целью поддержки атмосферы
респектабельности на сайте электронной коммерции. При наличии сертификата,
выданного хорошо известным центром сертификации, Web-браузер может устано-
вить SSL-соединение с сайтом, не выводя при этом никаких предупреждающих диало-
говых окон. Web-серверы, которые поддерживают SSL-соединения, часто называют
безопасными Web-серверами.
Безопасные Web-серверы
Для безопасной связи с Web-браузерами посредством протокола SSL можно ис-
пользовать сервер Apache, Microsoft IIS или любой другой бесплатный или коммерче-
ский Web-сервер. Применение сервера Apache позволяет пользоваться L’NIX-
подобной операционной системой, которая почти наверняка будет более надежной,
хотя и более трудной в установке, чем IIS. Естественно, сервер Apache доступен и для
платформы Windows.
Чтобы использовать SSL на сервере IIS, необходимо установить IIS. сгенерировать
пару ключей и установить свой сертификат. Для активизации протокола SSL на сер-
вере Apache потребуется установить три различных пакета: Apache, ModjSSL и
OpenSSL.
Добиться своей цели можно и купив пакет Stronghold. Пакет Stronghold — это
коммерческий продукт стоимостью примерно 1000 долларов США. который можно
загрузить из http: / /stronghold. redhat. com. Этот пакет построен на базе Apache, но
поставляется в ваде самоустанавливающегося бинарного файла, уже настроенного
для использования SSL. Таким образом, вы получаете надежность UNIX в сочетании с
простотой установки и технической поддержкой от производителя.
В приложении А приведены инструкции по установке двух наиболее популярных
Web-серверов — Apache и IIS. Можно сразу же начать использовать SSL, сгенерировав
собственный сертификат, однако посетителям вашего сайта будут выдаваться преду-
368
Часть III. Электронная коммерция и безопасность
преждения, что вы сами подписали свой сертификат. Для эффективного использова-
ния SSL потребуется получить сертификат у одного из центров сертификации.
Нюансы процесса получения сертификата зависят от конкретного центра серти-
фикации, но в общем случае вам нужно будет доказать, что вы являетесь легально
признанной организацией с физическим адресом, и эта организация владеет соот-
ветствующим именем домена.
Далее необходимо создать запрос на подпись сертификата (Certificate Signing
Request — CSR). Этот процесс варьируется от сервера к серверу. Соответствующие
инструкции доступны на Web-сайтах центров сертификации. Пакеты Stronghold и IIS
организуют этот процесс на базе диалоговых окон, а сервер Apache требует непо-
средственного ввода команд. Тем не менее, процесс по сути дела одинаков для всех
серверов. Его конечным результатом является получение зашифрованного запроса на
подпись сертификата. CSR должен выглядеть приблизительно так:
--BEGIN NEW CERTIFICATE REQUEST-
MIIBuwIBAAKBgQCLnlXX8faMHhtzStp9wY6BVTPuEU9bpMmhrb6vgaNZy4dTe6VS
84p7wGepq5CQjfOL4Hjda+gl2xzto8uxBkCD098Xg9q86CY45HZk+q6GyGOLZSOD
8cQHwhloUP65s5Tz018OFBzpI3bHxfO6aYelWYziDiFKplBrUdua+pK4SQIVAPLH
SV9FSz8Z7IH0glZr5H82oQ01AoGAWSPWyfVXPAF8h2GDb+cf97k44VkHZ+Rxpe8G
ghlfBn9L3ESWUZNOJMfDLlny7dStYU98VTVNekidYuaBsvyEkFrny7NCUmiuaSnX
4UjtFDkNhX9j5YbCRGLmsc865AT54KRu3102/dKHLo6NgFPirijHy99HJ4LRY9Z9
HkXVzswCgYBwBFH2QfK88C6JKW3ah+6cHQ4Deoiltxi627WN5HcQLwkPGn+WtYSZ
jG5tw4tqqogmJ+IP2F/5G6FI2DQP7QDvKNeAU8jXcuijuWo27S2sbhQtXgZRTZvO
jGn89BC0mIHgHQMkI7vz35mxlSkk3VNq3ehwhGCvJlvoeiv2J8X2IQIVAOTRp7zp
En7QIXnXwls 7xXbbuKPO
--END NEW CERTIFICATE REQUEST-
Вооружившись CSR-запросом, соответствующей суммой, документацией, доказы-
вающей ваше существование, а также убедившись, что имя используемого домена
совпадает с именем домена в бизнес-документах, можно зарегистрироваться в органе
сертификации для получения сертификата.
Когда орган сертификации выдаст сертификат, его следует сохранить в своей сис-
теме и указать Web-серверу, где его искать. Окончательный сертификат — это тексто-
вый файл, который выглядит подобно показанному выше CSR-запросу.
Аудит и регистрация
Используемая операционная система позволяет регистрировать любые события.
С точки зрения безопасности, интерес могут представлять следующие события:
ошибки сети, доступ к определенным файлам, таким как файлы конфигурации или
реестра NT, а также обращения к программам, таким как su (эта программа использу-
ется в Unix-системах, чтобы стать другим пользователем, как правило, привилегиро-
ванным).
Файлы регистрации (журналы) помогают обнаружить ошибочные или злонаме-
ренные действия по мере их возникновения. Они могут помочь также в понимании
того, что послужило причиной возникновения проблемы или каким образом пред-
принималась попытка проникновения, если проверить журналы после обнаружения
проблем. С файлами регистрации связаны две проблемы — размер и достоверность.
Глава 15. Безопасность сайта электронной коммерции
369
Если установить наиболее параноидальный критерий обнаружения и регистрации
проблем, в результате будут созданы огромные журналы, которые очень сложно ис-
следовать. Чтобы решить проблему больших журналов, необходимо воспользоваться
существующими утилитами или, руководствуясь стратегией защиты, создать собст-
венные сценарии поиска “интересных” событий в журнальных файлах. Процесс ау-
дита можно выполнять в реальном времени или же периодически.
Журнальные файлы также могут подвергаться атакам. Если взломщик обладает в
системе правами привилегированного пользователя или администратора, он может
изменить файлы журналов, дабы скрыть следы своей деятельности. Операционная
система Unix позволяет регистрировать события в журнале на другом компьютере. Это
значит, что взломщику, чтобы замести следы, придется проникнуть, по меньшей мере,
на два компьютера. Подобное можно сделать и в Windows, однако это не так легко.
Системный администратор может выполнять регулярный аудит, но, возможно,
периодически придется прибегать и к внешнему аудиту для проверки действий само-
го администратора.
Брандмауэры
Брандмауэры предназначены для изоляции локальной сети от внешнего мира.
Подобно тому, как противопожарные стены препятствуют распространению огня,
сетевые брандмауэры не позволяют хаосу проникать внутрь сети.
Брандмауэр (firewall) предназначен для защиты компьютеров в сети от атак извне.
Брандмауэр фильтрует или запрещает передачу данных, если данные не удовлетво-
ряют определенным правилам. Брандмауэр ограничивает действия компьютеров и
людей, которые находятся вне сети.
Иногда брандмауэр применяется для ограничения действий пользователей внутри
сети. Брандмауэр может ограничивать протоколы, доступные пользователям, запре-
щать соединения с определенными хостами сети и заставлять использовать прокси-
сервер для снижения стоимости полосы пропускания.
Брандмауэром может быть либо специальное устройство, такое как маршрутиза-
тор с поддержкой правил фильтрации, либо программа, выполняющаяся на компью-
тере. В любом случае, брандмауэру требуется доступ к двум сетям (внешней и внут-
ренней) и набор правил. Брандмауэр контролирует весь поток данных, который
проходит из одной сети в другую. Если поток данных удовлетворяет правилам, он
передается в другую сеть, в противном случае поток останавливается и отвергается.
Пакеты можно фильтровать по типам, адресам отправителя, адресам получателя
или по информации о портах. Некоторые пакеты могут быть просто отброшены, в то
время как другие могут регистрироваться в журналах и включать признак тревоги.
Резервное копирование данных
При любом плане восстановления после сбоя нельзя недооценивать важность ре-
зервного копирования. Оборудование и здания можно застраховать или поменять,
можно разместить сайт в другом месте, но никакая страховая компания не в состоя-
нии возместить потерю специализированного программного обеспечения для рабо-
ты в Web.
370
Часть III. Электронная коммерция и безопасность
Регулярно создавайте резервные копии всех компонентов Web-сайта — статиче-
ских страниц, сценариев и баз данных. Частота выполнения резервного копирования
зависит от того, насколько динамичен сайт. Если сайт полностью статичный, можно
обойтись резервным копированием при внесении изменений. Однако сайты, о кото-
рых идет речь в этой книге, вероятно, будут изменяться часто, особенно, если плани-
руется принимать заказы по сети.
Большинство сайтов приличных размеров должны устанавливаться на сервере с
дисковым массивом RAID (Redundant Array of Inexpensive Disks — массив недорогих
дисков с избыточностью), который может поддерживать зеркальное отображение.
Такой подход учитывает и возможные сбои жестких дисков. Тем не менее, следует
учитывать ситуации, когда что-то может случиться со всем массивом RAID, компью-
тером или вообще зданием.
Отдельные резервные копии следует делать настолько часто, чтобы это соответ-
ствовало объему производимых обновлений. Эти резервные копии должны хранить-
ся на отдельных носителях и, желательно, в отдельном и безопасном месте на случай
пожара, кражи или стихийного бедствия.
Существует масса приложений для резервного копирования и восстановления.
Мы же уделим основное внимание тому, как создавать резервные копии сайта, по-
строенного на основе РНР и базы данных MySQL.
Резервное копирование общих файлов
Воспользовавшись приложениями для резервного копирования, можно достаточ-
но легко создать резервные копии файлов HTML, РНР, изображений и других, не
относящихся к базам данных.
Из бесплатных программ наиболее широко используется утилита резервного ко-
пирования AMANDA (Advanced Maryland Automated Network Disk Archiver — усовер-
шенствованный автоматизированный архиватор сетевых дисков из Мериленда). Эта
утилита создана в Университете Мериленда. Она поставляется в составе многих ди-
стрибутивов UNIX и может использоваться для резервного копирования файлов
Windows-компьютеров через SAMBA. Дополнительную информацию об утилите
AMABDA можно найти на сайте http:/ /www. amanda. org.
Резервное копирование и восстановление
баз данных MySQL
Резервное копирование действующей базы данных значительно сложнее. Потре-
буется избежать копирования какой-либо таблицы, в которой в данный момент про-
изводятся изменения.
Инструкции по резервному копированию и восстановлению баз данных MySQL
приведены в главе 12.
Физическая безопасность
Рассмотренные до этого момента угрозы безопасности были связаны с такими не-
материальными факторами, как программное обеспечение. Но не следует забывать и
о физической безопасности системы. Необходимо помнить о кондиционировании
Глава 15. Безопасность сайта электронной коммерции
371
воздуха и противопожарной защите, защите от людей (как от злоумышленников, так
о то просто неуклюжих), сбоев электроснабжения и сбоев сети.
Система должна быть надежно заперта. В зависимости от размеров системы, это
может быть комната, клетка или шкаф. Сотрудники, которым не требуется физиче-
ский доступ к системе, и не должны его иметь. Не располагающие соответствующими
полномочиями сотрудники могут намеренно или случайно выдернуть какой-нибудь
шнур или попытаться обойти средства обеспечения безопасности с помощью загру-
зочной дискеты.
Противопожарные разбрызгиватели воды могут нанести такой же ущерб, как и
сам пожар. В прошлом во избежание ущерба использовались халоновые системы по-
жаротушения. В настоящее время производство халона запрещено Монреальским
протоколом о веществах, разрушающих озоновый слой, поэтому' в новых системах
должны использоваться другие, менее вредные вещества, такие как аргон или дву-
окись углерода. Более подробную информацию об этом можно получить на сайте
http://www.epa.gov/Ozone/snap/fire/qa.html.
Периодические кратковременные перебои в электроснабжении случаются везде.
В районах с неустойчивой погодой и наземными линиями электропередачи длитель-
ные перебои происходят регулярно. Если для вас важна постоянная работа системы,
потребуется вложить деньги в источник бесперебойного питания (Uninterruptible
Power Supply — UPS). Источник бесперебойного питания, который может поддержи-
вать питание одного компьютера в течение 10—30 минут, стоит менее 100 долларов
США. Увеличение длительности перебоев или объема оборудования влечет за собой
увеличение расходов. Для обеспечения работы как кондиционеров, так и компьюте-
ров в случае длительных перебоев в электроснабжении потребуется приобретение
электрогенератора.
Подобно перебоям в электроснабжении, невозможно контролировать как кратко-
временные (на протяжении нескольких минут), так и длительные (часовые) перебои
в Internet, хотя они иногда происходят. Если наличие соединения с сетью имеет
очень большое значение, имеет смысл использовать соединения с более чем одним
поставщиком Internet-услуг. Конечно, два соединения с Internet будут стоить дороже
одного, но в случае сбоя у вас все же останется ограниченное по пропускной способ-
ности соединение, а это лучше чем вообще ничего.
Проблемы подобного рода являются одной из побудительных причин размещения
компьютеров в специально предназначенных местах. Хотя не особо крупная органи-
зация не всегда может’ себе позволить использовать источники бесперебойного пи-
тания, которые обеспечивали бы работу системы дольше, чем в течение нескольких
десятков минут, равно как иметь избыточные соединения с Internet и дорогие проти-
вопожарные системы, все это доступно в специально оборудованных офисных здани-
ях, предоставляющих место под солнцем сотням подобных организаций.
Что дальше
В главе 16 мы детально остановимся на аутентификации — обеспечении пользова-
телей возможностью подтвердить свою личность. Будут рассмотрены различные ме-
тоды аутентификации посетителей, включая и те, которые применяются в РНР и
MySQL.
372
Часть III. Электронная коммерция и безопасность
16
Реализация задачи
аутентификации с
помощью РНР и MySQL
В этой главе мы обсудим реализацию различных технологий, использующих ме-
ханизмы РНР и MySQL, для аутентификации пользователей.
В главе, помимо прочих, рассматриваются следующие темы:
Идентификация посетителей сайта.
Реализация контроля доступа.
Базовая аутентификация.
Использование базовой аутентификации в РНР.
Использование базовой аутентификации с помощью файлов .htaccess серве-
ра Apache.
Использование базовой аутентификации в IIS.
Использование аутентификации с помощью модуля mod_auth_mysql.
Создание собственного метода аутентификации.
Идентификация посетителей
Web — это достаточно анонимная среда, однако часто полезно знать, кто конкрет-
но посетил ваш сайт. К счастью для конфиденциальности посетителей, без их содей-
ствия можно получить только очень незначительную информацию.
Приложив некоторые усилия, серверы могут получить достаточно много инфор-
мации о соединенных с ними компьютерах и сетях. Обычно Web-браузер идентифи-
цирует себя, указывая свой тип, версию и операционную систем}', под управлением
которой он выполняется. Существует возможность с помощью JavaScript определить
разрешение и цветовую глубину, установленные на мониторах посетителей, а также
размеры окон браузеров.
Каждый подключенный к Internet компьютер имеет уникальный IP-адрес. Из IP-
адреса посетителя можно извлечь определенную информацию. Можно узнать, кто
владеет этим адресом, а иногда, с некоторой долей вероятности, можно предполо-
жить географическое местоположение посетителя. Одни адреса предоставляют
большую информацию, нежели другие. В основном, те, кто используют постоянные
подключения к Internet, обладают постоянными IP-адресами. В то же время, в боль-
шинстве случаев клиенты, которые дозваниваются к поставщикам Internet-услуг, по-
лучают один из IP-адресов поставщика во временное пользование. В следующий раз
этот адрес может использоваться другим компьютером, и когда вы увидите пользова-
теля, который посещал сайт ранее, у него, возможно, будет другой 1Р-адрес.
К счастью для пользователей Web, никакая информация, которую выдает браузер,
не позволяет установить личность пользователя. Если хотите выяснить имя посети-
теля и другие сведения, придется спросить его об этом.
Многие Web-сайты вынуждают пользователей предоставлять о себе информацию.
Например, доступ к бесплатной электронной версии газеты New York Times
(http: //www.nytimes.com) предоставляется только тем, кто согласен сообщить о се-
бе такие сведения, как имя, пол и общий семейный доход. Сайт новостей и дискуссий
Slashdot (http://www.slashdot.org) позволяет зарегистрированным пользователям
участвовать в дискуссиях под псевдонимами и настраивать для себя интерфейс сайта.
Большинство сайтов электронной коммерции записывают сведения о своих клиентах
при оформлении первого заказа. Это означает, что покупателю не придется вводить
информацию о себе в каждом будущем заказе.
Запросив и получив в ответ информацию о посетителе, ее необходимо каким-то
образом связать с посетителем при его последующих посещениях сайта. Если пред-
положить, что с определенного компьютера, используя определенное имя пользова-
теля на этом компьютере, на сайт заходит только один пользователь, а каждый поль-
зователь работает только на одном компьютере, то для идентификации пользователя
можно создать cookie-набор на компьютере пользователя. Для большинства пользо-
вателей подобное предположение неверно. Часто многие люди совместно пользуют-
ся одним и тем же компьютером, а многие используют несколько компьютеров. По
крайней мере, иногда приходится повторно спрашивать пользователя о том, кто он
такой. Кроме того, придется попросить посетителя предоставить определенные до-
казательства того, что он тот, за кого себя выдает.
Как упоминалось в главе 13, предложение пользователю подтвердить свою лич-
ность называется аутентификацией (authentication). Обычный метод аутентификации
в Web сводится к требованию к посетителям предоставить уникальное имя пользова-
теля и пароль. Аутентификация обычно служит для разрешения или запрещения дос-
тупа к определенным страницам или ресурсам. Аутентификация может быть необяза-
тельной либо использоваться для других целей, например, для персонализации сайта.
Реализация контроля доступа
Простой контроль доступа реализовать несложно. Код, приведенный в листинге
16.1, выводит одну из трех возможных страниц. Если этот файл загружен без пара-
метров, будет отображаться HTML-форма с приглашением ввести имя пользователя и
пароль (рис. 16.1).
Если при загрузке параметры присутствуют, но они были введены неправильно,
отображается сообщение об ошибке (рис. 16.2).
374
Часть III. Электронная коммерция и безопасность
Рис. 16.1. HTML-форма выводит запрос на ввод посетителями
имени пользователя и пароля для получения доступа к опреде-
ленной странице
Рис. 16.2. Когда посетители вводят неправильные сведения,
необходимо вывести сообщение об ошибке. В реальном сайте,
вероятно, придется вывести более дружественное сообщение
Если эти параметры присутствуют и они правильные, посетителю отображается
“секретное” содержимое (рис. 16.3).
Код для создания функций, проиллюстрированных на рисунках 16.1, 16.2 и 16.3,
показан в листинге 16.1.
Код в листинге 16.1 реализует простой механизм аутентификации, позволяющий
санкционированным посетителям видеть защищенную страницу, однако он содержит
несколько существенных проблем.
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
375
Рис. 16.3. Когда имя и пароль указаны правильно, сценарий
выводит “секретное” содержимое
Листинг 16.1. secret .php — РНР- и HTML-код для реализации простого
механизма аутентификации
<?php
// Создание коротких имен переменных
@ $name = $_POST['name’];
@ $password = $_POST['password’];
if(empty($name)||empty($password))
(
// Посетитель должен ввести имя и пароль
?>
<М>Пожалуйста, введите свое имя пользователя и пароль<'hl>
Эта страница является секретной.
<form method = "post" action = "secret.php">
<table border = "1">
<tr>
<th> Имя пользователя </th>
<td> cinput type = "text" name = "name"> </td>
</tr>
<tr>
<th> Пароль </th>
<td> <input type = "password" name = "password"> </td>
</tr>
<tr>
<td colspan = "2" align = "center">
<input type = "submit” value = "Войти">
</td>
</tr>
</table>
</form>
<?php
}
376
Часть III. Электронная коммерция и безопасность
else if($name=='user' && $password=='pass')
(
// Комбинация имени пользователя и пароля посетителя верна
echo '<М>Вы на месте’</hl>';
echo 'Я полагаю, вы счастливы лицезреть эту секретную страницу.';
}
else
{
// Комбинация имени и пароля посетителя неверна
echo ' <Ы>Немедленно покиньте эту страницу! </hl >';
echo 'Вам не разрешено просматривать данный ресурс.';
}
?>
Описанный сценарий:
поддерживает только одно жестко закодированное имя пользователя и пароль;
хранит пароль в виде открытого текста;
защищает только одну страницу;
передает пароль в виде открытого текста.
Эти проблемы можно разрешить, приложив различные усилия и добиваясь раз-
личных успехов.
Хранение паролей
Для хранения паролей существует множество мест, более подходящих, нежели код
сценария. Внутри сценария очень трудно изменять данные. Можно написать сцена-
рий, который будет изменять себя, но это не особо удачная идея. При этом на серве-
ре придется хранить выполняющийся на нем сценарий, доступный для записи и
изменений со стороны других пользователей. Хранение паролей на сервере в от-
дельном файле позволит без труда написать программу для добавления и удаления
пользователей, а также для изменения паролей.
Внутри сценария или другого файла данных существует ограничение на количест-
во пользователей, которых можно обслуживать без серьезного снижения общей про-
изводительности сценария. Если планируется сохранять большое количество эле-
ментов в файле или производить поиск среди большого числа элементов, то, как
обсуждалось ранее, следует рассмотреть возможность использования базы данных
вместо двумерного файла. В качестве практического совета можно принять следую-
щее утверждение: если вы собираетесь хранить и производить поиск в более чем 100
элементах, следует отдать предпочтение базе данных.
Использование базы данных для хранения имен и паролей посетителей не сильно
усложнит сценарий, но позволит быстро проводить аутентификацию множества
пользователей. Это также упростит создание сценария для добавления и удаления
пользователей, а также даст возможность пользователям изменять свои пароли.
Сценарий для аутентификации посетителей страницы с использованием базы
данных показан в листинге 16.2.
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
377
Листинг 16.2. secretdb.php — использование MySQL для улучшения простого
механизма аутентификации
<?php
$name = $_POST['name'];
$password = $_POST['password'];
if(!isset($_POST['name'])&&!isset($_POST['password']))
{
// Посетитель должен ввести имя и пароль
?>
<111>Пожалуйста, введите свое имя пользователя и пароль</Ь1>
Эта страница является секретной.
<form method="post" action="secretdb.php">
<table border="l">
<tr>
<th> Имя пользователя </th>
<td> <input type="text" name="name"> </td>
</tr>
<tr>
<th> Пароль </th>
<td> <input type = "password" name = "password"> </td>
</tr>
<tr>
<td colspan ="2" align = ”center”>
<input type = "submit" value = "Войти">
</td>
</tr>
</table>
</form>
<?php
)
else
{
// Подключение к mysql
$mysql = mysqli_connect( 'localhost', 'webauth', 'webauth' );
iff!$mysql)
{
echo 'Невозможно подключиться к базе данных.';
exit;
}
11 Выбор соответствующей базы данных
$selected = mysqli_select_db( $mysql, 'auth' );
iff!$mysql)
f
echo 'Невозможно выбрать базу данных.';
exit;
}
//Запрос к базе данных с целью выяснения существования соответствующей записи
$query = "select count(*) from authorised_users where
name = '$name' and
pass = '$password'";
$result = mysqli_query( $mysql, $query );
378
Часть III. Электронная коммерция и безопасность
if (!$result)
{
echo ’Невозможно выполнить запрос. ' ;
exit;
}
$row = mysqli_fetch_row( $result );
$count = $result[0];
if ( $count > 0 )
{
// Комбинация имени и пароля посетителя верна
echo ' <Ы>Вы на месте!</hl>';
echo 'Я полагаю, вы счастливы лицезреть эту секретную страницу.';
else
{
// Комбинация имени и пароля посетителя неверна
echo '<Ы>Немедленно покиньте эту страницу!</hl>';
echo 'Вам не разрешено просматривать данный ресурс.';
Используемую в примере базу данных можно создать, подключившись к MySQL в
качестве привилегированного пользователя и запустив сценарий, приведенный в
листинге 16.3.
Листинг 16.3. createauthdb.php — эти MySQL-запросы создают базу данных auth,
таблицу authorisedusers и двоих пользователей
create database auth;
use auth;
create table authorised_users (
name
pass
primary key
varchar(20),
varchar(40),
(name)
insert into authorised_users values
('username', 'password');
insert into authorised_users values
( 'testuser', shal('password') );
grant select on auth.*
to 'webauth'
identified by 'webauth';
flush privileges;
Шифрование паролей
Независимо от того, где хранятся пароли — в базе данных или в файле — хранение
паролей в виде простого текста сопряжено с неоправданным риском. Однонаправ-
ленный алгоритм хеширования обеспечивает дополнительную защиту при незначи-
тельных дополнительных затратах.
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
379
РНР предоставляет несколько однонаправленных хеш-функций. Наиболее старой
и наименее защищенной из них является функция crypt (). Алгоритм вычисления
дайджеста Message Digest 5 (MD5), реализованный в функции md5(), более строг и
доступен в последних версиях РНР. Если вам не нужна совместимость со старыми
версиями РНР, пользуйтесь алгоритмом Secure Hash Algorithm 1 (SHA-1).
РНР-функция shal () реализует строгий однонаправленный криптографический
хеш-алгоритм. Прототип этой функции выглядит следующим образом:
string shal (string str [, bool raw_output])
Получив на входе строку str, эта функция возвращает псевдослучайную 40-
символьную строку. Если в качестве raw_output передать true, функция возвратит
20-символьную строку двоичных данных. Например, для строки "password" функция
shal() вернет строку "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8". Эта строка не
может быть дешифрована и превращена обратно в "password" даже ее создателем,
поэтому7 на первый взгляд она может казаться не столь уж полезной. Что делает
функцию shal () полезной, так это то, что ее результат строго детерминирован. Для
одной и той строки shal () будет возвращать один и тот же результат при каждой ее
вызове.
Таким образом, вместо следующего РНР-кода:
if( Susername == ’user’ && $password == ’pass’ )
{
// Пароль совпадает
}
можно воспользоваться кодом:
if( $username == ’user’ &&
shal($password) == ’5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8’ )
{
// Пароль совпадает
}
Перед использованием функции shal () нам не требуется знать, как выглядела
строка пароля. Достаточно знать, совпадает ли введенный пароль с тем, для которого
применялась shal ().
Как уже упоминалось, жесткое кодирование правильных имен и паролей посети-
телей в сценарии — совершенно неудачная идея. Для их хранения следует использо-
вать отдельный файл или базу данных.
Если для хранения данных аутентификации выбрана база данных MySQL, можно
воспользоваться PHP-функцией shal () или MySQL-функцией SHA1 (). MvSQL предла-
гает больший спектр алгоритмов хеширования по сравнению с РНР. однако все они
служат одной и той же цели.
Чтобы воспользоваться функцией SHA1 (), SQL-запрос в листинге 16.2 следует пе-
реписать, например, так, как показано ниже:
select count!*) from authorised_users where
name = ’$username’ and
password = shal(’$password’)
380
Часть III. Электронная коммерция и безопасность
Этот запрос будет подсчитывать количество строк в таблице authorised_users, в
которых значение поля name совпадает с содержимым переменной Sname, а значение
поля password совпадает с результатом применения функции SHA1 () к переменной
$password. Учитывая, что мы заставляем посетителей выбирать уникальные имена,
результатом запроса может быть 0 или 1.
Не забывайте, что хеш-функции в общем случае возвращают данные фиксирован-
ного размера. В случае SHA1 () это 40 символов при представлении в виде строки.
Убедитесь, что соответствующий столбец в базе данных имеет достаточную ширину.
Еще раз просмотрев код в листинге 16.3. легко заметить, что мы создали одного
пользователя (' username ') с незашифрованным паролем и еще одного пользователя
(' testuser') — с зашифрованным паролем.
Защита нескольких страниц
Защита более чем одной страницы с помощью сценариев наподобие показанных в
листингах 16.1 и 16.2 немного сложнее. Поскольку в HTTP-протоколе отсутствует ме-
ханизм поддержки состояний, то не существует автоматической связи или ассоциа-
ции между последовательными запросами, поступающими от одного и того же посе-
тителя. Это усложняет перенос введенных пользователем данных, таких как данные
аутентификации, между страницами.
Наиболее простой способ защиты нескольких страниц — использование механиз-
мов контроля доступа, предоставляемых Web-сервером. Вскоре мы рассмотрим эти
механизмы.
Чтобы самостоятельно создать такие функциональные возможности, придется
включить части листинга 16.1 в код каждой страницы, которую необходимо защитить.
С помощью директив auto_prepend_f ile и auto_append_f ile требуемый файл мож-
но автоматически вставить в начало (prepend) или в конец (append) каждого файла в
указанных каталогах. Использование упомянутых директив обсуждалось в главе 5.
Что же происходит, когда пользователь открывает несколько страниц сайта, если
воспользоваться таким подходом? Понятно, что недопустимо запрашивать пароль
отдельно для каждой страницы, которую желает просмотреть пользователь.
Введенную пользователем информацию можно было бы включить в каждую гипер-
ссылку на странице. Поскольку пользователи могут указывать пробелы или другие сим-
волы, применение которых запрещено в Internet-адресах, следует обратиться к функ-
ции urlencode (), выполняющей безопасное кодирование подобных символов.
Однако с этим подходом связаны еще некоторые проблемы. Поскольку данные ау-
тентификации будут присутствовать в отправляемых посетителю Web-страницах,
защищенные страницы, которые посетил пользователь, могут быть просмотрены
любым человеком, работающим за тем же компьютером. Для этого достаточно про-
смотреть кэшированные копии страниц или заглянуть в список посещенных страниц
браузера. Поскольку пароль пересылается в браузер и обратно вместе с каждой за-
прошенной или предоставленной страницей, чувствительная информация передает-
ся чаще, чем это необходимо.
Решить проблему можно с помощью двух механизмов — базовой НТТР-аутен-
тификации и поддержки сеансов. Базовая аутентификация позволяет решить про-
блему кэширования, но сервер все равно отправляет пароль Web-браузеру в каждом
запросе. Управление сеансами позволяет решить обе проблемы. Сначала мы рас-
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
381
смотрим базовую HTTP-аутентификацию, а затем в главах 22 и 26 подробно исследу-
ем вопросы управления сеансами.
Базовая аутентификация
К счастью, аутентификация пользователей — это достаточно распространенная
задача, и существуют возможности аутентификации, встроенные в протокол HTTP.
Сценарии и Web-серверы могут запрашивать аутентификацию у Web-браузера. После
этого Web-браузер должен вывести на экран диалоговое окно или нечто подобное и
запросить у пользователя необходимую информацию.
Хотя Web-сервер запрашивает новые сведения аутентификации в каждом запросе
пользователя, Web-браузеру нет необходимости запрашивать эту информацию для
каждой страницы. В общем случае браузер хранит сведения аутентификации до тех
пор, пока открыто его окно, и автоматически отправляет их без вмешательства со
стороны пользователя.
Это свойство протокола HTTP называется базовой аутентификацией (basic authen-
tication). Базовую аутентификацию можно включить средствами РНР или с помощью
механизмов, встроенных в Web-сервер. Далее рассматриваются методы, предпола-
гающие использование РНР, сервера Apache и сервера IIS.
Базовая аутентификация передает имя пользователя и пароль в виде открытого
текста и поэтому не особо безопасна. Протокол HTTP 1.1 предоставляет более безо-
пасный метод, называемый аутентификаиией с помощью дайджеста (digest authen-
tication). Этот метод использует алгоритм хеширования (как правило, MD5) для
сокрытия подробностей выполнения транзакции. Аутентификация с помощью
дайджеста поддерживается многими Web-серверами и большинством современных
браузеров. К сожалению, она не поддерживается многими устаревшими браузерами,
которые все еще находятся в употреблении, и, кроме того, версия стандарта, реали-
зованная в Microsoft Internet Explorer и Microsoft IIS, не совместима с продуктами
других компаний.
В дополнение к очень слабой поддержке в наборе доступных браузеров, аутенти-
фикация с помощью дайджеста к тому же и не очень-то безопасна. И базовая, и дай-
джест-аутентификация обеспечивают низкий уровень безопасности. Ни один из этих
методов не дает пользователю гарантий, что он работает именно с тем компьютером,
доступ к которому он планировал получить. Оба метода позволяют взломщику' повто-
рить тот же запрос серверу. Поскольку базовая аутентификация передает пароль
пользователя в открытом виде, любой взломщик, способный перехватывать пакеты,
может сымитировать любой запрос пользователя.
Базовая аутентификация обеспечивает (низкий) уровень безопасности, подобный
тому, который обеспечивается при подключении по протоколу Telnet или FTP. Эти
методы также передают пароли в виде простого текста. Аутентификация с помощью
дайджеста несколько более безопасна, поскольку она шифрует пароли перед переда-
чей. Комбинация базовой аутентификации с протоколом SSL и цифровыми сертифи-
катами позволяет надежно защитить все части транзакций в Web.
Методы надежной защиты рассматриваются в главе 17. Однако во многих ситуа-
циях наиболее подходящим будет быстрый и относительно незащищенный метод,
такой как базовая аутентификация.
382
Часть III. Электронная коммерция и безопасность
Базовая аутентификация позволяет защитить именованные области и требует от
пользователей ввода правильного имени и пароля. Ввиду того, что области имено-
ванные, на одном сервере может существовать множество областей. Различные фай-
лы и каталоги на одном сервере могут принадлежать разным областям, каждая из ко-
торых защищена своими наборами имен пользователей и паролей. Именованные
области позволяют также сгруппировать в одну область несколько каталогов на од-
ном физическом или виртуальном хосте и защитить всю область одним паролем.
Использование базовой
аутентификации в РНР
В общем случае PHP-сценарии являются кросс-платформенными, но в основе ис-
пользования базовой аутентификации лежат переменные среды, устанавливаемые
сервером. Сценарий HTTP-аутентификации должен определять тип сервера и вести
себя соответствующим образом в зависимости от того, выполняется он как модуль
Apache на сервере Apache или как ISAPI-модуль на сервере IIS. Показанный в листин-
ге 16.4 сценарий будет выполняться на обоих серверах.
Листинг 16.4. http .php — базовую HTTP-аутентификацию можно включить
средствами РНР
<?php
// Если используется сервер IIS, потребуется установить переменные среды
if (substr($SERVER_SOFTWARE, 0, 9) == 'Microsoft' &&
!isset($PHP_AUTH_USER) &&
!isset($PHP_AUTH_PW) &&
substr($HTTP_AUTHORIZATION, 0, 6) == 'Basic ’
)
{
list($PHP_AUTH_USER, $PHP_AUTH_PW) =
explode)':', base64_decode(substr($HTTP_AUTHORIZATION, 6)));
)
// Замените этот оператор if запросом к базе данных или чем-то подобным
if ($PHP_AUTH_USER != 'user' || $PHP_AUTH_PW != 'pass')
{
// Посетитель еще не предоставил детали либо его
// имя пользователя и пароль некорректны
header ( 'WWW-Authenticate: Basic realm="Realm-Name" ') ;
if (substr($SERVER_SOFTWARE, 0, 9) == 'Microsoft')
header('Status: 401 Unauthorized');
else
header('HTTP/1.0 401 Unauthorized');
echo '<hl>Немедленно покиньте эту страницу!</hl>';
echo 'Вам не разрешено просматривать данный ресурс.';
}
else
{
// Посетитель предоставил корректную информацию
echo '<Ы>Вы на месте!</hl>';
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
383
echo '<р>Я полагаю, вы счастливы лицезреть эту секретную страницу.</р>';
}
7>
Код в листинге 16.4 работает так же, как и код из предшествующих листингов этой
главы. Если пользователь не передал данных аутентификации, ему выдается запрос
на аутентификацию. Если пользователь предоставил неправильную информацию, для
него отображается сообщение об отказе в доступе. Если же пользователь предоставил
соответствующую пару имя—пароль, он увидит содержимое страницы.
Интерфейс данного примера несколько отличается от интерфейса предыдущих
примеров. Мы не создаем HTML-форму для ввода имени и пароля. Диалоговое окно
для аутентификации выведет браузер пользователя. Некоторые считают это улучше-
нием. другие предпочитают иметь полный контроль над визуальными аспектами ин-
терфейса. На рис. 16.4 показано диалоговое окно входа, которое выводит браузер
Internet Explorer.
... ..
» с
Подключение к МЧА0824
Попьэоеатель: j
Пароль: Г
Г~ Содзанить пароль
I OK j Отмена !
| ^Открьггиесгра*4цыЬф^ $ Интернет
Рис. 16.4. При использовании HTTP-аутентификации внешний
вид диалогового окна определяется браузером пользователя
Поскольку аутентификация поддерживается встроенными возможностями брау-
зеров, последние демонстрируют некоторую осторожность в обработке неудачных
попыток аутентификации. Internet Explorer предоставляет пользователю три попыт-
ки для аутентификации, и если все они оказываются неудачными, выводит сообще-
ние об отказе в доступе. Netscape Navigator предоставляет неограниченное число
попыток, но между попытками выводит диалоговое окно с запросом о повторе "Au-
thentication Failed. Retry?" ("Аутентификация была неудачной. Повторить по-
пытку?"). Netscape отображает сообщение об отказе в доступе, только если пользова-
тель щелкает на кнопке Cancel (Отмена).
Как и код из листингов 16.1 и 16.2, код данного примера можно поместить во все
страницы, которые требуется защитить, или автоматически вставить его в начало
каждого файла в каталоге.
384
Часть III. Электронная коммерция и безопасность
Использование базовой аутентификации
с помощью файлов .htaccess сервера Apache
Результата, подобного результату выполнения предыдущего сценария, можно дос-
тичь и без PHP-сценария. Сервер Apache предоставляет множество различных мето-
дов аутентификации, которые можно применять для определения правильности вве-
денных пользователем данных. Наиболее простой из этих методов предполагает
использование функциональных возможностей модуля rr.cd_auth, который сравнива-
ет пары имя—пароль со строками текстового файла, хранящегося на сервере.
Чтобы вывод на экране пользователя был аналогичен результату выполнения пре-
дыдущего сценария, потребуется создать два HTML-файла — один для содержимого
страницы и один для сообщения об отказе в доступе. В предыдущих примерах были
опущены некоторые элементы HTML-кода, но на самом деле в HTML-файлах присут-
ствуют HTML-дескрипторы <html> и <body>.
В листинге 16.5 показано содержимое файла content.html. которое будут видеть
пользователи, успешно прошедшие аутентификацию. Листинг 16.6 содержит HTML-
код страницы rejection.html с сообщением об отказе в доступе. Создавать страницу,
которая будет отображаться в случае ошибки, вовсе не обязательно, но наличие та-
кой страницы с полезной информацией свидетельствует о хорошем стиле и должном
профессионализме. Учитывая, что такая страница будет отображаться пользователю
при неудачной попытке войти в “закрытую” зону, полезной может оказаться инфор-
мация о том, как зарегистрироваться и получить пароль, или как его переопределить
и отправить по электронной почте, если вдруг пароль был забыт.
Листинг 16.5. content.html — пример содержимого
<htmlxbody>
<Ы>Бы на месте!</hl>
< р>Я полагаю, вы счастливы лицезреть эту секретную страницу.</р>
</bodyx/html>
Листинг 16.6. rejection.html — простое сообщение об ошибке 401
<htmlxbody>
< Ь1>Немедленно покиньте эту страницу!</hl>
< р>Вам не разрешено просматривать этот ресурс.</р>
</bodyx/html>
В этих файлах нет ничего нового. Для данного примера интерес представляет
файл из листинга 16.7. Этот файл должен иметь имя .htaccess. Он будет управлять
доступом к файлам и подкаталогам каталога, в котором он расположен.
Листинг 16.7. .htaccess — с помощью файла .htaccess можно установить различные
параметры сервера Apache, включая активизацию аутентификации
ErrorDocument 401 /chapterl6/rejection.html
AuthUserFile /home/book/.htpass
AuthGroupFile /dev/null
AuthName "ReaIm-Name"
AuthType Basic
require valid-user
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
385
В листинге 16.7 показано содержимое файла .htaccess, которое обеспечивает
включения базовой аутентификации в каталоге. С помощью этого файла можно уста-
навливать различные параметры, однако в этом примере все шесть строк относятся к
аутентификации.
Первая строка:
ErrorDocument 401 /chapterl6/rejection.html
указывает серверу Apache, какой документ следует отобразить посетителям, не про-
шедшим аутентификацию. Директиву ErrorDocument можно использовать несколько
раз в одном файле, чтобы предоставить собственные страницы для сообщений о дру-
гих ошибках протокола HTTP, например, об ошибке 404. Синтаксис этой директивы
таков:
ErrorDocument номер_ошибки URL
Важно, чтобы страница с сообщением об ошибке 401 была доступна для всех посе-
тителей. Не имеет смысла поддерживать собственную страницу с сообщением о не-
удачной аутентификации, если эта страница располагается в каталоге, в который
можно попасть только после успешного прохождения аутентификации.
Строка
AuthUserFile /home/book/.htpass
указывает серверу, где искать файл, содержащий пароли пользователей. Часто этот
файл имеет имя .htpass, но можно выбрать произвольное имя файла. Не важно как
этот файл называется, но важно, где он хранится. Он должен храниться в рамках де-
рева Web-каталогов — там, откуда пользователи могут его загрузить с помощью Web-
сервера. Содержимое файла .htpass для данного примера показано в листинге 16.8.
Листинг 16.8. .htpass — файл паролей хранит имена и зашифрованные пароли
каждого пользователя
userl:0nRp9M80GS7zM
user2:nC13sOTOhp.ow
user3:yjQMCPWjXFTzU
user4:LOmlMEi/hAme2
Кроме указания отдельных санкционированных пользователей, можно указать,
что доступ к ресурсу разрешен только прошедшим аутентификацию пользователям из
определенной группы. В данном примере эта возможность не используется, и строка
AuthGroupFile /dev/null
устанавливает параметр AuthGroupFile равным /dev/null — специальный файл в
системах на базе Unix, который является гарантированно пустым.
Как и в примере с РНР, для использования HTTP-аутентификации защищаемой
области следует присвоить имя. Это выполняет следующая строка:
AuthName "Realm-Name"
Имя области может быть произвольным, но следует помнить, что это имя будет
видно посетителям. Дабы еще раз напомнить, что имя области в этом примере следу-
ет изменить, было выбрано имя "Realm-Name" ("Имя-области").
386
Часть III. Электронная коммерция и безопасность
Поскольку сервер поддерживает различные методы аутентификации, следует ука-
зать, какой именно метод будет использоваться:
AuthType Basic
Также необходимо задать, кому разрешен доступ. Можно указать определенных
пользователей, определенные группы или, как в данном примере, всех санкциониро-
ванных пользователей.
Строка
require valid-user
определяет, что доступ разрешен всем пользователям, успешно прошедшим аутенти-
фикацию.
Каждая строка в файле .htpass содержит имя пользователя и зашифрованный
пароль, разделенные двоеточием.
Конкретное содержимое этого файла может быть различным. Чтобы создать та-
кой файл, воспользуйтесь небольшой программой htpasswd, которая поставляется
вместе с сервером Apache.
Эту программу можно использовать одним из двух следующих способов:
htpasswd [-cmdps] файл_пароля имя_пользователя
или
htpasswd -b[cmdps] файл_пароля имя_пользователя пароль
Единственным ключом, который потребуется указывать, является -с. Этот ключ
уведомляет программу htpasswd, что требуется создать новый файл. Используйте
этот аргумент для создания первого пользователя. Будьте внимательны и не приме-
няйте его для следующих пользователей, поскольку если файл уже существует, то
программа htpasswd удалит его и создаст новый файл с тем же именем.
Необязательные ключи m, d, р, и s следует применять для выбора алгоритма шиф-
рования (включая отказ от шифрования).
Ключ Ь заставляет программу ожидать пароль в качестве параметра, а не запраши-
вать его отдельно. Этот аргумент полезен для запуска программы htpasswd в неинте-
рактивном режиме, в качестве части командного файла, но его не следует использо-
вать при вызове программы из командной строки.
Приведенные ниже команды использовались для создания файла, показанного в
листинге 16.8.
htpasswd -be /home/book/.htpass userl passl
htpasswd -b /home/book/.htpass user2 pass2
htpasswd -b /home/book/.htpass user4 pass3
htpasswd -b /home/book/.htpass user4 pass4
Обратите внимание, что путь к программе htpasswd может отсутствовать в пути
поиска; если это так, потребуется указывать полный путь к этой программе. В большин-
стве систем htpasswd хранится в каталоге /usr/local/apache/bin.
Аутентификацию такого типа легко настроить, однако с ней связано несколько про-
блем.
Имена и пароли пользователей хранятся в текстовом файле. Каждый раз, когда
браузер запрашивает файл, защищенный с помощью .htaccess. сервер должен про-
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
387
анализировать этот файл, а затем еще и файл пароля, чтобы попытаться найти соот-
ветствующее имя пользователя и пароль. Вместо использования файла .htaccess то
же самое можно указать в файле httpci.conf — главном файле конфигурации Web-
сервера. В то время как файл .htaccess анализируется при каждом запросе, файл
httpci.conf анализируется только в момент запуска сервера. Подобный подход уско-
рит обработку запросов, но для внесения изменений придется остановить и переза-
пустить сервер.
Независимо от места хранения директив сервера, файл паролей анализируется
при каждом запросе. Это означает, что подобно другим рассмотренным технологиям,
в которых используется двумерный файл, данный метод не годится в с л у чае поддерж-
ки сотен или тысяч посетителей.
Использование базовой
аутентификации в IIS
Подобно серверу Apache, сервер IIS также поддерживает НТТР-аутентификацию.
В Apache используется подход, свойственный системам UNIX — управление сервером
осуществляется путем редактирования текстовых файлов. Как можно догадаться,
сервером IIS можно управлять, выбирая настройки в диалоговых окнах.
В среде Windows ХР конфигурацию Internet Information Server 5 (IIS5) можно из-
менить с помощью утилиты Internet Information Services (Информационные службы
Internet), которая расположена в группе Administrative Tools (Администрирование)
панели управления.
Окно Internet Information Services выглядит подобно показанному на рис. 16.5.
Древовидное представление в левой части окна показывает, что на компьютере с
именем YNA0824 выполняется несколько служб. Нас интересует служба Default Web
Site (Веб-узел по умолчанию). В этом сайте имеется каталог protected, который со-
держит файл content. html.
‘f Internet Infortwion Service*
' Консоль Действие Вид Справка
i о - © ® Э Ш $ Ж. * » Л
i internet Information Service? (Описание_____, 'jCocTO... Имя заголовка,.. • АдресР : Порт j Состояине
i j - Jsi ^Веб’уэел по умолчанию Работа * A-аче-пя 60
- Веб-узел по умол^ j
‘ $ IISHdp (
+ Pmters t
• Skpnpmy$q!3e •
j; ♦ i ПринтерЗ !
' .J Принтерд j
м ♦ J Принтер <
!i • _J Узлы FTP
i J ♦ -Ъ Виртуальный SMTP-»?}
i
Рис. 16.5. Приложение Internet Information Services позволяет конфи-
гурировать сервер Internet Information Server 5
388
Часть III. Электронная коммерция и безопасность
Чтобы включить базовую аутентификацию для каталога protected, щелкните
правой кнопкой мыши на этом каталоге и выберите пункт Properties (Свойства) в
контекстном меню.
Диалоговое окно Properties (Свойства) позволяет изменять многие параметры для
этого каталога. Для нас представляют интерес вкладки Directory Security (Безопас-
ность каталога) и Custom Errors (Специальные ошибки). Обратите внимание на раз-
дел Anonymous Access and Authentication Control (Анонимный доступ и проверка под-
линности) на вкладке Directory Security. Щелчок на кнопке Edit (Изменить) приводит
к отображению диалогового окна, показанного на рис. 16.6.
Заголовки HTTP j
Каталог } Документы
Анонимный доступ и проверка подлинности
Специальные ошибки
Безопасность каталога
|\1етоды проверки
R Анонимный доступ
Для доступа к данному ресурсу не требуется имя и пароль
Безопаг
Учетная запись для анонимного доступа.
Имя пользователя. |rjSr\t4a6824 Обзор
Пароль
Р" Разрешить управление паролем из IIS
Доступ с проверкой подлинности
Для этих методов проверки подлинности имя и перо ль
пользователя требуются в следующих случаях:
- анонимный доступ отключен,
-доступ ограничен таблицами управления доступом NTFS
Г
Г Обычная (пароль отправляется е текстовом формате]
Домен по умолчанию |
Область- ;
р' Встроенная проверка подлинности Windows
ОК | Отмене J Справка |
Рис. 16.6. По умолчанию IIS5 разрешает аноним-
ный доступ, но позволяет активизировать аутенти-
фикацию
В этом диалоговом окне можно отключить анонимный доступ и включить базовую
аутентификацию. Если установить параметры в соответствие с рисунком 16.6, то
файлы в выбранном каталоге смогут просматривать только те пользователи, которые
ввели соответствующее имя и пароль.
Чтобы продублировать поведение предшествующих примеров, нужно еще создать
страницу', сообщающую пользователю о вводе некорректных данных аутентифика-
ции. Перейти на вкладку Custom Errors можно после закрытия диалогового окна
Authentication Methods (Методы проверки подлинности).
Вкладка Custom Errors, показанная на рис. 16.7, позволяет связать ошибки с сооб-
щениями об ошибках. В этом примере мы воспользовались применявшимся ранее
файлом сообщения об отказе в доступе rejection.html, который был показан в лис-
тинге 16.6. Сервер IIS позволяет выводить более точное, нежели сервер Apache, со-
общение об ошибке, поскольку в дополнение к коду ошибки IIS выдает еще и причину
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
389
ее появления. Для ошибки 401, связанной с неудачной аутентификацией, сервер IIS
предоставляет возможность указания пяти различных причин. Для каждой из причин
можно выводить свое сообщение.
Рис. 16.7. Вкладка Custom Errors позволяет ассоции-
ровать собственные страницы с сообщениями об
ошибках с соответствующими событиями ошибок
Это все, что нужно проделать в плане включения аутентификации для выбранного
каталога в сервере IIS5. Как и другие Windows-приложения, сервер IIS5 настроить
проще, чем аналогичную службу в UNIX, но сложнее копировать с компьютера на
компьютер или из каталога в каталог. Кроме того, сервер IIS5 можно случайно на-
строить так, что ваш компьютер окажется вообще незащищенным.
Большая проблема с сервером IIS состоит в том, что он аутентифицирует пользо-
вателей, сравнивая переданную информацию с учетными записями на том же компь-
ютере. Если нужно разрешить доступ пользователю "John" с паролем "password", на
этом компьютере или в его домене потребуется создать учетную запись для этого
пользователя с тем же именем и паролем. При создании учетных записей для аутен-
тификации в Web следует проявлять особую осторожность. Удостоверьтесь, что для
таких учетных записей установлены только те права, которые необходимы для про-
смотра Web-страниц, и что учетная запись не обладает правами доступа к другим
службам наподобие Telnet.
Использование аутентификации с помощью
модуля mod_auth_mysql
Как уже упоминалось ранее, можно легко и эффективно использовать модуль
mod_auth на сервере Apache. Однако этот модуль не очень-то подходит для загружен-
390
Часть III. Электронная коммерция и безопасность
пых сайтов с большим количеством пользователей, поскольку mod_auth хранит реги-
страционные сведения пользователей в текстовом файле.
К счастью, модуль mod_auth_mysql сочетает в себе почти такую же простоту
применения, как и для модуля mod_auth, и скорость работы баз данных. Он работа-
ет подобно mod_auth, но благодаря использованию базы данных MySQL вместо тек-
стового файла, он способен быстрее производить поиск в больших списках пользо-
вателей.
Чтобы использовать этот модуль, его следует скомпилировать и установить, или
попросить сделать это системного администратора.
Установка модуля mod_auth_mysql
Чтобы можно было использовать модуль mod_auth_mysql, сначала следует устано-
вить сервер Apache и MySQL в соответствии с инструкциями из приложения А. Затем
необходимо выполнить еще несколько шагов. Достаточно подробные инструкции
приведены в файлах README и USAGE дистрибутива, однако в ряде мест они ссылаются
на поведение предыдущих версий. Ниже кратко описаны основные действия.
1. Получите архив дистрибутива этого модуля. Он находится на прилагаемом к
книге компакт-диске, а последняя версия модуля доступна для загрузки по сле-
дующему адресу:
http://sourceforge.net/projects/mod-auth-mysql/
2. Воспользуйтесь утилитами zip и tar для распаковки исходного кода.
3. Перейдите в каталог mod_auth_mysql, запустите make, а затем make install.
Возможно, потребуется изменить расположение установленной копии MySQL
в make-файле (Makefile).
4. Добавьте в файл httpd.conf следующую строку, которая обеспечит динамиче-
скую загрузку модуля в Apache:
LoadModule mysql_auth_module libexec/mod_auth_mysql.so
5. Создайте в MySQL базу данных и таблицу, в которой будут храниться данные
аутентификации. Для этого не обязательно создавать отдельную базу данных и
таблицу. Можно воспользоваться существующими таблицами базы данных
authorised_users, которая упоминалась в более ранних примерах этой главы.
6. Добавьте в файл httpd.conf строку параметров для модуля mod_auth_mysql.
Эти параметры будут использоваться для подключения к базе данных, а строка
в файле конфигурации выглядит следующим образом:
Auth_MySQL_Infо имя_хоста имя_пользователя пароль
Ну, как, работает?
Простейший способ проверки, работают ли скомпилированные модули — это
проверить, запускается ли сервер Apache. При наличии поддержки SSL для запуска
сервера Apache необходимо ввести:
/usr/local/apache/bin/apachectl startssl
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
391
Если поддержка SSL отсутствует, для запуска сервера Apache можно ввести:
/usr/local/apache/bin/apachectl start
Если сервер запустится с директивой Auth_MySQL_Info в файле конфигурации
httpd.conf. значит, модуль mod_auth_mysql успешно установлен.
Использование модуля mod_auth_mysql
После того как модуль mod_auth_mysql успешно установлен, его использовать не
намного сложнее, чем модуль mod_auth. В листинге 16.9 показан пример файла
.htaccess, который позволит аутентифицировать пользователей с зашифрованными
паролями, которые будут храниться в базе данных, созданной ранее в этой главе.
Листинг 16.9. .htaccess — этот файл включает аутентификацию пользователей
с применением базы данных MySQL
ErrorDocument 401 /chapterl6/rejection.html
AuthName ”Realm-Name”
AuthType Basic
Auth__MySQL_DB auth
Auth_MySQL_Encryption_Types MySQL
Au th_MySQL_Pas sword_Table au thori sed_users
Auth_MySQL_Username_Field name
Auth_MySQL_Password_Field password
require valid-user
Как видите, большая часть этого файла совпадает с файлом, приведенным в лис-
тинге 16.7. Мы по-прежнему указываем файл с сообщением об ошибке 401 (неудачная
аутентификация). Снова указана базовая аутентификация и имя области аутентифи-
кации. И, как в листинге 16.7, доступ разрешен любому' пользователю, который ус-
пешно прошел аутентификацию.
Поскольку применяется модуль mod_auth_mysql, и не хотелось бы использовать
установки по умолчанию, в листинге 16.9 указаны дополнительные директивы для
настройки.
Директивы Auth_MySQL_DB, Auth_MySQL_Password_Table, Auth_MySQL_Username_Field
и Auth_MySQL_Password_Field используются для указания, соответственно, имени
базы данных, таблицы и полей имени пользователя и пароля.
В листинге присутствует директива Auth_MySQL_Encryption_Types, которая обес-
печивает выбор типа шифрования. В примере выбрано шифрование паролей MySQL.
Для этой директивы приемлемыми являются значения Plaintext, Crypt_DES или
MySQL. Значение Crypt_DES используется по умолчанию, и оно активизирует исполь-
зование стандартного для Unix алгоритма шифрования паролей DES.
С точки зрения пользователя, пример с модулем mod_auth_mysql работает в точ-
ности так же, как пример с модулем mod_auth. В браузере пользователя отображается
диалоговое окно. Если пользователь проходит аутентификацию, он увидит защищен-
ное содержимое страницы, а если нет — сообщение об ошибке.
Для большинства Web-сайтов аутентификация с помощью модуля mod_auth_mysql
подходит практически идеально. Этот метод быстро работает, его сравнительно лег-
392
Часть III. Электронная коммерция и безопасность
ко реализовать и он позволяет использовать любой удобный механизм для создания
новых учетных записей в базе данных. Для большей гибкости и контроля над частями
Web-страниц, возможно, придется создать собственный метод аутентификации с
применением РНР и MySQL.
Создание собственного метода
аутентификации
В этой главе мы рассмотрели способы создания собственных методов аутентифи-
кации. При этом мы ознакомились с некоторыми недостатками и компромиссами, а
также со встроенными методами аутентификации, которые обеспечивают меньшую
гибкость, чем применение специально созданного кода. После изучения методов
управления сеансами появится возможность создавать собственные методы аутенти-
фикации с меньшим количеством компромиссов, нежели в этой главе.
В главе 22 рассматривается создание простой системы аутентификации пользова-
телей, которая, благодаря использованию сеансов для передачи переменных между
страницами, позволяет избежать некоторых проблем, описанных в этой главе.
В главе 26 будет показано применение этого подхода в реальном проекте. В ней вы
узнаете, как его можно использовать для реализации качественной системы аутенти-
фикации.
Дополнительные источники информации
HTTP-аутентификация подробно описана в документе RFC 2617, который досту-
пен по следующему’ адресу:
http://www.rfc-editor.org/rfc/rfc2617.txt
Документацию по модулю mod_auth, управляющему базовой аутентификацией на
сервере Apache, можно найти по адресу:
http: //www.apache. org/docs/mod/mod„auth. html
Документацию по модулю mod_auth__mysql можно найти в выгружаемом архиве.
Этот выгружаемый архив очень мал, поэтому его стоит выгрузить и прочесть файл
readme даже в том случае, если просто требуется побольше узнать о нем.
Что дальше
В следующей главе описана методика защиты данных на всех стадиях обработки —
на стадии ввода, передачи и хранения. В ней рассмотрено использование SSL, циф-
ровых сертификатов и шифрования.
Глава 16. Реализация задачи аутентификации с помощью РНР и MySQL
393
17
Реализация безопасных
транзакций с помощью
РНР и MySQL
В этой главе объясняется, как безопасно обрабатывать пользовательские данные
при вводе, пересылке и сохранении. Это позволяет выполнять транзакции меж-
ду пользователем и сервером, защищенные с обеих сторон.
В главе, помимо прочих, рассматриваются следующие темы:
Обеспечение безопасности транзакций.
Использование протокола защищенных сокетов (SSL).
Обеспечение безопасного хранения данных.
Определение необходимости хранения номеров кредитных карточек
Использование шифрования в РНР.
Обеспечение безопасности транзакций
Обеспечение безопасности транзакций в Internet сводится к проверке потока
информации в системе и обеспечению того, что в каждой точке она является за-
щищенной. В контексте сетевой безопасности не существует некоего абсолюта. Ни
одна система не может считаться полностью недоступной для проникновения. Под
безопасностью здесь понимается то, что уровень усилий, необходимых для взлома сис-
темы или пересылаемой информации, сравним со значимостью этой информации.
Чтобы усилия по защите были эффективными, необходимо проверять прохожде-
ние потока информации во всех частях системы. Поток пользовательской информа-
ции в типичном приложении, написанном с использованием РНР и MySQL, показан
на рис. 17.1.
Особенности каждой транзакции, происходящей в системе, могут быть различ-
ными в зависимости от внутренней структуры системы, от пользовательских данных,
а также действий, послуживших источником транзакции. Их можно исследовать сле-
дующим образом. Каждая транзакция между Web-приложением и пользователем на-
чинается с того, что браузер пользователя отправляет через Internet запросы к Web-
серверу. Если страница содержит PHP-сценарий, то Web-сервер передает полномо-
чия обработки страницы механизму РНР.
Рис. 17.1. Пользовательская информация хранится или обрабатывается в показанных
на рисунке элементах среды типичного Web-приложения
PHP-сценарий может считывать или записывать данные на диск. Он также мо-
жет включать РНР- или HTML-файлы с использованием функций include () или
require () и отправлять SQL-запросы демону MySQL, получая от него ответы. Меха-
низм MySQL отвечает за считывание и запись собственных данных на диск.
Такая система состоит из трех основных частей:
Компьютера пользователя.
Собственно Internet.
Системы сервера.
В следующих разделах мы рассмотрим вопросы безопасности для каждой из час-
тей, но, разумеется, компьютер пользователя и Internet находятся вне пределов ва-
шего контроля.
Компьютер пользователя
На компьютере пользователя действует Web-браузер — и это все, что вы о нем
знаете. Вы не можете управлять такими факторами, как настройка безопасности это-
го компьютера. Более того, необходимо помнить, что компьютер может быть абсо-
лютно незащищенным или даже служить в качестве общедоступного терминала в
библиотеке, школе или Internet-кафе.
Существует несколько различных браузеров, предоставляющих разные возможно-
сти. Если принять во внимание только последние версии двух популярных браузеров,
то большинство различий в них влияет лишь на то, как форматируется и выводится
HTML-код, однако нам требуется исследовать вопросы безопасности и функциони-
рования.
Следует обратить внимание, что ряд пользователей могут отключать свойства, ко-
торые, по их мнению, могут представлять опасность для системы или угрозу конфи-
денциальности, например, Java, cookie-наборы или поддержку JavaScript. Если вы ис-
пользуете эти свойства, то либо убедитесь, что ваше приложение работает в условиях
их отключения, либо обеспечьте упрощенный интерфейс, который позволит упомя-
нутым пользователям успешно взаимодействовать с вашим сайтом.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
395
Пользователи за пределами США и Канады могут использовать Web-браузеры, ко-
торые поддерживают только 40-битное шифрование. Хотя в январе 2000 года прави-
тельство СИТА изменило законодательство, позволив экспортировать усложненное
шифрование (в страны, не подпадающие под эмбарго), и 128-битное шифрование
стало доступным большинству пользователей, некоторые из них могли до сих пор не
обновить версии своих браузеров. Если только вы не гарантируете безопасность сво-
им пользователям в тексте своего сайта, вам, как Web-разработчику, нет надобности
беспокоиться об этих вопросах. SSL автоматически позволяет серверу и браузеру
пользователя взаимодействовать на максимально возможном уровне безопасности.
К сожалению, нельзя быть уверенным в том, что именно Web-браузер соединяется
с сайтом, используя предназначенный для этого интерфейс. Запросы к сайту могут
поступать с другого сайта, “заимствующего” изображения или данные, или же от кого-
либо, использующего программное обеспечение наподобие библиотеки cLTRL с це-
лью обхода средств защиты.
О библиотеке cURL, позволяющей эмулировать запросы браузера, будет рассказа-
но в главе 19. Она полезна для разработчиков, однако может быть использована и в
злонамеренных целях.
Хотя мы и лишены возможности изменять или управлять настройками компьюте-
ров пользователей, беспокоиться об этом не следует. Различия между компьютерами
пользователей влияют лишь на то, какие функциональные возможности можно обес-
печить в сценариях серверной (РНР), а какие — клиентской (JavaScript) стороны.
Функциональные возможности, предоставляемые РНР, могут быть совместимы с
любым браузером, поскольку конечным результатом является лишь HTML-страница.
Использование чего-то более сложного, нежели самый примитивный вариант
JavaScript, приводит к необходимости учета различий между возможностями версий
отдельных браузеров.
С точки зрения защиты, для проверки данных стоит использовать серверные сце-
нарии, так как в этом случае исходный код не будет виден пользователю. При про-
верке данных средствами JavaScript пользователи могут видеть исходный код и, воз-
можно, перехитрить его.
Все требуемые данные можно сохранять на сервере (в виде файлов и записей базы
данных) или на компьютере пользователя (в форме cookie-наборов). Использование
cookie-наборов для сохранения ограниченных данных (ключ сеанса) рассматривается
в главе 22.
Большая часть хранящихся данных должна находиться на Wciy-cepncpc или в базе
данных. Существует несколько веских причин, по которым на компьютере пользова-
теля следует хранить как можно меньше информации. Если информация хранится за
пределами системы, нельзя быть уверенным в том, насколько надежно она хранится,
что пользователь не удалит ее, или же не изменит ее. чтобы попытаться ввести сис-
тему в заблуждение.
Internet
Как и в случае компьютера пользователя, характеристики Internet также не под-
даются контролю, однако их нельзя игнорировать при проектировании системы.
Internet обладает множеством замечательных свойств, однако принципиально не
является безопасной сетью. При пересылке информации из одной точки в другую
396
Часть III. Электронная коммерция и безопасность
следует помнить, что ее могут просматривать и даже изменять другие пользователи.
Об этом упоминалось в главе 13. Памятуя об этом, можно решить, что следует пред-
принять:
Переслать информацию в любом случае, не забывая, что при этом она может
оказаться доступной другим лицам и по пути может быть изменена.
Поставить цифровую подпись под информацией перед пересылкой, чтобы за-
щитить ее от подделки.
Зашифровать информацию перед пересылкой, чтобы сохранить ее в тайне и
защитить от подделки.
Решить, что информация является слишком чувствительной, чтобы допускать
какую-либо возможность перехвата, и подыскать другие способы ее распро-
странения.
Internet — это еще и исключительно анонимная среда. Очень трудно удостоверить-
ся, что лицо является именно тем, за кого себя выдает. Даже если в этом можно убе-
диться для собственного спокойствия, будет непросто предоставить достаточно веские
доказательства этого таким организациям, как, например, суд. А это порождает про-
блемы, связанные с отказом от обязательств, о которых упоминалось в главе 15.
Короче говоря, приватность и отказ от обязательств — это весьма большие про-
блемы при выполнении транзакций через Internet.
Существует, по меньшей мере, два способа защиты информации, поступающей на
или с Web-сервера через Internet:
SSL (Secure Sockets Layer — протокол защищенных сокетов).
S-HTTP (Secure Hypertext Transfer Protocol — протокол защищенной передачи
гипертекста).
Обе технологии обеспечивают приватный, защищенный от взлома обмен сооб-
щениями и аутентификацию, однако если SSL широко распространен, то S-HTTP
применяется пока нечасто. SSL подробно рассматривается далее в этой главе.
Ваша система
Ваша система является той частью вселенной, которой вы можете полностью
управлять. Ее компоненты обрамлены на рис. 17.1 прямоугольником. Эти компонен-
ты могут быть физически разделены и соединены сетью, либо же существовать на
одном компьютере.
Можно практически не беспокоиться о безопасности информации, пересылкой
которой через Web занимаются продукты независимых разработчиков. Авторы этих
программ уделили им достаточное внимание. Если используется последняя версия
широко распространенного продукта, то обо всех известных на текущий момент
проблемах можно узнать, воспользовавшись средствами, которые предоставляет сайт
Google, или любыми другими средствами поиска в Web. Очень важно постоянно сле-
дить за обновлением информации подобного рода.
Если инсталляция и конфигурирование входят в ваши обязанности, следует по-
беспокоиться о том, как проинсталлировано и сконфигурировано программное
обеспечение. Немало ошибок в защите системы являются результатом пренебре-
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
397
жения предупреждениями в документации или же связаны с общими вопросами
системного администрирования, которые могут послужить темой для отдельной
книги. Поэтому советуем либо приобрести хорошую книгу по администрированию
используемой операционной системы, либо же нанять эксперта по этому вопросу.
Следует отметить, что в общем случае более безопасной (и эффективной) являет-
ся установка РНР в качестве SAPI-модуля Web-сервера, а не его запуск через CGI-
интерфейс.
Прежде всего, следует позаботиться о том, что должны, а чего не должны делать
ваши сценарии.
Какие потенциально чувствительные к перехвату данные приложение будет пере-
сылать пользователю через Internet? Какие данные оно будет запрашивать у пользо-
вателя? Если пересылается информация, представляющая собой приватную транзак-
цию между вами и пользователем, необходимо прибегнуть к SSL.
Здесь уже обсуждался вопрос применения SSL между компьютером пользователя и
сервером. Следует также рассмотреть ситуацию, когда данные пересылаются по сети
от одного компонента системы другому. Так происходит, например, тогда, когда база
данных MySQL находится на одном компьютере, а Web-сервер — на другом. РНР со-
единяется с сервером MySQL по протоколу TCP/IP, а такое соединение не шифрует-
ся. Если оба компьютера находятся в локальной сети, следует убедиться, что она на-
дежно защищена. Если они взаимодействуют через Internet, то система, возможно,
будет работать несколько медленнее, а к соединению необходимо относиться так же,
как и к любому другому Internet-соединению.
Когда пользователи думают, что имеют дело с вами, очень важно, чтобы это дей-
ствительно было именно так. Регистрация с помощью цифрового сертификата по-
может защитить посетителей от обмана (когда кто-то другой пытается выдать свой
сайт за ваш), позволит использовать SSL, не выдавая пользователям предупреждаю-
щего сообщения, и придаст респектабельность всему предприятию.
Проверяют ли сценарии данные, вводимые пользователями? Действительно ли
защищена сохраняемая информация? Ответы на эти вопросы даны в последующих
разделах этой главы.
Использование протокола защищенных
сокетов (SSL)
Семейство протоколов защищенных сокетов (SSL) изначально было разработано
компанией Netscape с целью обеспечения безопасного соединения Web-серверов и
Web-браузеров. С тех пор SSL стал неофициальным стандартным методом для обмена
чувствительной информацией между браузерами и серверами.
Достаточно хорошо поддерживаются SSL как версии 2, так и версии 3. Большин-
ство Web-серверов либо включают функциональные возможности SSL, либо допуска-
ют их добавление в виде подключаемого модуля. Браузеры Internet Explorer и
Netscape Navigator поддерживают SSL, начиная с версии 3.
Обычно сетевые протоколы и реализующее их программное обеспечение органи-
зуются в виде набора (стека) уровней. Каждый уровень может передавать данные или
запрашивать службы из уровня, расположенного выше или ниже. Такой стек прото-
колов показан на рис. 17.2.
398
Часть III. Электронная коммерция и безопесность
HTTP I FTP | SMTP I
__________TCP/UDP
_____________IP
Прочие протоколы
Прикладной уровень
Транспортный уровень
Сетевой уровень
Переходный уровень между хостом и сетью
Рис. 17.2. Стек протоколов, используемый протоколом
уровня приложений, таким как протокол передачи гипер-
текста (HTTP)
Когда для пересылки информации используется протокол HTTP, он вызывает
протокол TCP (Transmission Control Protocol — протокол управления передачей), ко-
торый, в свою очередь, связан с протоколом IP (Internet Protocol — Internet-
протокол). Последний нуждается в соответствующем протоколе, управляющем сете-
вым оборудованием и преобразующем пакеты данных в электрические сигналы, ко-
торые отправляются в точку назначения.
HTTP называют протоколом уровня приложений. Протоколами такого типа яв-
ляются FTP, SMTP и Telnet (как показано на рис. 17.2), а также POP, IMAP и ряд дру-
гих. TCP — это один из двух протоколов транспортного уровня, используемого в сетях
TCP/IP. IP — протокол сетевого уровня. Уровень между хостом и сетью отвечает за
соединение хоста (компьютера) с сетью. Протоколы этого уровня не указаны в стеке
TCP/IP. поскольку для различных типов сетей на этом уровне требуются различные
протоколы.
При отправке данные пересылаются по стеку из приложения к физической сете-
вой среде. При получении данные проходят через стек от физической сети к прило-
жению.
Использование SSL добавляет к описанной модели еще один прозрачный уровень.
Уровень SSL находится между транспортным уровнем и уровнем приложений. Эта
модель показана на рис. 17.3. Прежде чем передать поступившие от НТТР-приложе-
ния данные транспортному уровню для отправки по месту назначения, уровень SSL
изменяет их.
Рис. 17.3. SSL добавляет дополнительный уровень к стеку протоколов, а также уров-
ни приложений для управления своими действиями
SSL может обеспечить среду безопасной передачи и другим протоколам кроме
HTTP. Возможность использования других протоколов связана с тем, что, по сути,
уровень SSL является прозрачным, то есть он обеспечивает такой же интерфейс про-
токолам выше него, как и расположенный под ним транспортный уровень. Поэтому
он прозрачно обеспечивает квитирование, шифрование и дешифрацию.
Когда Web-браузер соединяется с безопасным Web-сервером по HTTP, обеим сторо-
нам необходимо воспользоваться протоколом установки соединения (квитирования),
позволяющим договориться о таких действиях, как аутентификации и шифрование.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
399
Последовательность квитирования включает в себя следующие шаги:
1. Браузер соединяется с сервером, поддерживающим SSL, и запрашивает у него
аутентификацию.
2. Сервер отправляет свой цифровой сертификат.
3. Сервер может необязательно (и редко) запрашивать аутентификацию у браузера.
4. Браузер посылает список поддерживаемых им алгоритмов шифрования и хе-
ширования. Сервер выбирает наиболее надежное шифрование из числа под-
держиваемых им.
5. Браузер и сервер генерируют ключи сеанса:
а. Браузер получает открытый ключ сервера из его цифрового сертификата и
использует его для шифрования случайного числа.
б. Сервер отвечает отправкой случайных данных в текстовом формате (если
только по запросу сервера браузер не выслал цифровой сертификат — в
этом случае сервер использует открытый ключ браузера).
в. На основе этих случайных данных с помощью хеш-функций генерируются
ключи шифрования для сеанса.
Генерирование качественных случайных данных, дешифрация цифровых серти-
фикатов, генерирование ключей и использование шифрования с открытым ключом
требует времени, поэтому процедура квитирования выполняется не мгновенно. К
счастью, результаты кэшируются, поэтому, если тем же самым браузеру и серверу
требуется обменяться несколькими шифрованными сообщениями, процесс квитиро-
вания и соответствующая обработка выполняются только один раз.
При пересылке данных по SSL-соединению выполняются следующие действия:
1. Данные разбиваются на управляемые пакеты.
2. Каждый пакет сжимается (необязательно).
3. Каждый пакет содержит код аутентификации сообщения (message authentica-
tion code — МАС), вычисленный с помощью алгоритма хеширования.
4. МАС-код и сжатые данные объединяются и шифруются.
5. Зашифрованные пакеты объединяются с информацией заголовков и пересы-
лаются по сети.
Весь процесс показан на рис. 17.4.
Из приведенной схемы видно, что TCP-заголовок добавляется после шифрования
данных. Это значит, что маршрутная информация может быть перехвачена. И хотя
шпионы не смогут получить информацию, они довольно точно смогут определить,
кто ею обменивается.
400
Часть III. Электронная коммерция и безопасность
Код аутентификации сообщения (МАС)
Зашифрованные пакеты
Исходные данные
Пакеты данных
Сжатые данные
ТСР-пакеты
Рис. 17.4. Перед отправкой данных SSL разбивает их на пакеты, сжимает, хеши-
рует и шифрует
Причина, по которой сжатие в SSL выполняется до шифрования, состоит в том,
что хотя большая часть трафика в сети сжимается до пересылки, зашифрованные
данные плохо поддаются сжатию.
Алгоритмы сжатия основаны на поиске повторяющихся последовательностей
данных. Поэтому попытка их применения после того, как в результате шифрования
данные были превращены, по сути дела, в случайный набор битов, в большинстве
случаев оказывается бесполезной. Было бы нежелательным, чтобы SSL, разработан-
ный для повышения безопасности сети, приводил к существенному увеличению тра-
фика.
Хотя SSL сравнительно сложен, пользователи и разработчики могут не беспоко-
иться об этом, поскольку его внешние интерфейсы полностью повторяют сущест-
вующие протоколы.
В относительно недалеком будущем SSL 3.0 будет, скорее всего, заменен стандар-
том TLS 1.0 (Transport Layer Security — безопасность транспортного уровня), но на
момент написания книги TLS являлся лишь проектом стандарта, который не поддер-
живается пока ни одним сервером или браузером. TLS был задуман в качестве дейст-
вительно открытого стандарта, а не стандарта, который был разработан одной орга-
низацией и лишь впоследствии стал доступным для остальных. Он построен на
основе SSL 3.0, однако содержит ряд улучшений, связанных с преодолением недос-
татков SSL и повышением гибкости.
Проверка данных, вводимых пользователем
Один из принципов создания надежного Web-приложения заключается в том,
чтобы никогда не доверять данным, введенным пользователем. До того как помещать
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
401
их в файл или в базу данных либо передавать на выполнение системной команде,
данные, поступившие от пользователя, должны быть обязательно проверены.
В нескольких местах в этой книги обсуждались методы проверки вводимых поль-
зователями данных. Ниже приведено их краткое описание.
Функцию addslashes () следует использовать для фильтрации данных до их
пересылки в базу данных. Она отменит значения символов, которые могут вы-
зывать проблемы при сохранении в базе данных. Для возврата данных к исход-
ному виду служит функция stripslashes ().
Можно включить директивы magic_quotes_gpc и magic_quotes_runtime в
файле php.ini. Они автоматически добавляют или убирают управляющие
символы обратной косой черты, причем magic_quotes_gpc выполняет
это для входных переменных методов GET, POST и cookie-наборов, а
magic_quote_runtime — для данных, входящих или исходящих из баз данных.
Функцию escapeshellcmd() следует использовать при передаче данных от
пользователя таким командам, как system)) или ехес (), или применительно к
символу обратной одиночной кавычки. Функция отменяет действие любого
метасимвола, который может быть использован злонамеренным пользовате-
лем для запуска определенных системных команд.
Для удаления из строки HTML- и РНР-дескрипторов можно воспользоваться
функцией strip_tags (). Это не позволит создавать сценарии внутри данных,
которые сервер передает для отображения браузеру.
Функция htmlspecialchars() предназначена для преобразования некоторых
символов в HTML-эквиваленты. Например, символ < преобразуется в <.
В результате любые дескрипторы сценария будут преобразованы в безопасные
символы.
Обеспечение безопасного хранения данных
Три различных типа сохраняемых данных (HTML- или PHP-файлы, данные сце-
нариев и данные MySQL) часто размещаются в разных областях одного и того же
диска, однако на рис. 17.1 они показаны отдельно. Каждый тип требует своих мер
предосторожности, поэтому и обсуждаться они будут по отдельности.
Наиболее опасным является исполняемое содержимое. На Web-сайте таковым
обычно являются сценарии. Необходимо проявлять особую осторожность при опре-
делении прав доступа внутри Web-иерархии. Под этой иерархией здесь подразумева-
ется дерево каталогов, начинающееся с htdocs на сервере Apache или inetpub на
сервере IIS. Посторонние должны иметь возможность только читать сценарии, но ни
в коем случае не перезаписывать или вносить в них правки.
Это же относится и к каталогам внутри Web-иерархии. Только их владельцы
должны обладать правами записи в них. Другие пользователи, включая и того, под
чей учетной записью запускается Web-сервер, не должны иметь прав доступа для за-
писи или создания новых файлов в каталогах, которые могут быть загружены с Web-
сервера. Если разрешить запись файлов в эти каталоги, станет возможным создание
злонамеренных сценариев и их запуск из Web-сервера.
402
Часть III. Электронная коммерция и безопасность
Если сценариям требуется записывать в файлы, для этой цели необходимо создать
каталог за пределами Web-дерева. В особенности это касается сценариев загрузки
файлов. Сценарии и записываемые ими данные не должны перемешиваться.
При записи важных данных вначале можно попытаться их зашифровать. Однако
подобный подход редко оказывается эффективным.
Предположим, что на Web-сервере имеется файл creditcardnumbers.txt, и
взломщику удалось получить доступ к серверу и прочесть этот файл. Что еще он мо-
жет прочесть? Чтобы зашифровать и дешифровать данные, требуются соответст-
вующие программы и один или несколько файлов ключей. Если взломщик может
прочесть данные, скорее всего, ничто не помешает ему прочесть файлы ключей, рав-
но как и другие файлы.
Шифрование данных на Web-сервере имеет смысл только в том случае, если про-
граммное обеспечение и ключи для дешифрации хранятся на другом компьютере.
Один из способов обработки важных данных состоит в их шифровании на сервере и
последующей пересылки на другой компьютер, возможно, по электронной почте.
Содержимое базы данных похоже на данные, хранящиеся в обычных файлах дан-
ных. Если MySQL настроен корректно, то только эта система может записывать ин-
формацию в свои файлы. А это значит, что беспокоиться следует только о доступе
пользователей в рамках среды MySQL. В книге уже обсуждались вопросы, связанные с
системой управления полномочиями MySQL, которая присваивает определенные
права определенным пользователям на указанных хостах.
Особого упоминания заслуживает следующий факт: часто в PHP-сценариях необ-
ходимо указать пароль для доступа к MySQL. PHP-сценарии зачастую являются обще-
доступными для загрузки. Однако это не представляет такой проблемы, как может
показаться на первый взгляд. Если конфигурация Web-сервера не нарушена, исход-
ный код РНР никогда не будет виден извне.
Если Web-сервер сконфигурирован для синтаксического анализа файлов с расши-
рением . php с помощью интерпретатора РНР, внешние пользователи не имеют воз-
можности увидеть исходный код. Тем не менее, нужно соблюдать осторожность при
использовании других расширений. Если файлы с расширением . inc разместить в
Web-каталогах, по внешнему запросу можно будет получить исходный код. В этом
случае необходимо либо поместить подключаемые файлы за пределами Web-дерева и
сконфигурировать сервер так, чтобы он не пересылал файлы с таким расширением,
либо использовать для них только расширение . php.
Если Web-сервер вместе с вами используют и другие, то ваши пароли MySQL могут
быть видны другим пользователям, которые работают на этом же компьютере и могут
запускать сценарии через тот же Web-сервер. В зависимости от того, как сконфигу-
рирована система, упомянутая ситуация может оказаться неизбежной. Выходом мо-
жет послужить либо настройка Web-сервера таким образом, чтобы сценарии выпол-
нялись от имени отдельных пользователей, либо запуск отдельного экземпляра Web-
сервера для каждого пользователя. Если вы не являетесь администратором Web-
сервера (скорее всего, это так, раз вы используете его совместно с другими), имеет
смысл обсудить проблемы с администратором и изучить возможные способы защиты.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
403
Определение необходимости хранения
номеров кредитных карточек
При обсуждении безопасного хранения важных данных один их тип заслуживает
особого внимания. Пользователей Internet буквально охватывает паранойя при мыс-
ли о номерах кредитных карточек. Поэтому, если вы собираетесь хранить их на сво-
ем сайте, следует проявить предельную осторожность. Кроме того, следует задаться
вопросом, в действительности ли это необходимо.
Какие действия предполагается выполнять с номером кредитной карточки? Если
осуществляется одноразовая транзакция и обработка карточки в реальном времени,
лучше просто получить номер от клиента и направить его сразу в шлюз транзакций,
вообще не сохраняя номер на сервере.
Если необходимо производить периодические отчисления, например, изымать
ежемесячные взносы с одной и той же кредитной карточки, упомянутый подход не
подойдет. В этом случае номера карточек следует хранить за пределами Web-сервера.
Если вы все же собираетесь хранить данные о кредитных карточках множества
клиентов, вам потребуется профессиональный системный администратор, возмож-
но, немного параноик в вопросах безопасности, который тратил бы достаточное
время на изучение наиболее свежей информации по защите операционной системы и
других используемых программ.
Использование шифрования в РНР
Простая, но полезная задача, демонстрирующая применение шифрования, заклю-
чается в отправке зашифрованных сообщений электронной почты. Стандартом де-
факто многие годы был PGP — Pretty Good Privacy (очень надежная приватность). Фи-
липп Р. Циммерман (Philip R. Zimmermann) создал стандарт PGP специально для то-
го, чтобы придать сообщениям электронной почты должный уровень конфиденци-
альности.
Существуют бесплатные версии PGP, однако это программное обеспечение не яв-
ляется свободно распространяемым. Бесплатную версию можно применять только в
некоммерческих целях.
Если вы являетесь гражданином США и находитесь в пределах США, или гражда-
нином Канады в пределах Канады, можете получить бесплатную версию PGP с сайта
по адресу:
http://web.mit.edu/network/pgp.html
Для использования PGP в коммерческих целях можно получить лицензию у ком-
пании PGP Corporation. Соответствующие подробности находятся на сайте по адресу:
http://www.pgp.com
Для использования свободной версии PGP за пределами США и Канады следует
обратиться к интернациональной странице PGP:
http://www.pgpi.org
404
Часть III. Электронная коммерция и безопасность
Недавно появилась альтернатива PGP с открытым исходным кодом. GPG — Gnu
Privacy' Guard (хранитель приватности GNU) — представляет собой свободно распро-
страняемую замену PGP. Он не содержит запатентованных алгоритмов и поэтому
может использоваться в коммерческих целях без каких-либо ограничений.
Оба этих продукта выполняют свою задачу сходными способами. На уровне ко-
мандной строки разница практически незаметна, но каждая из них обладает своим
интерфейсом, например, модулями для популярных почтовых программ, которые
автоматически дешифруют сообщения при его получении.
GPG можно загрузить из следующего сайта:
http://www.gnupg.org
Оба этих продукта можно использовать совместно, создав, например, зашифро-
ванное сообщение с помощью GPG и переслав его лицу, использующему' PGP для де-
шифрации (если это последняя версия). Поскольку' нас интересует создание сообще-
ний на Web-сервере, ниже показан пример с использованием GPG. Применение в
данном случае PGP практически не потребовало бы изменений.
Как и в случае других примеров, приведенных в этой книге, данный пример тре-
бует наличия работающей программы GPG. Возможно, пакет GPG уже установлен в
системе. Если это не так. не стоит беспокоиться: процедура инсталляции проста, хотя
настройка и требует некоторых хитростей.
Инсталляция GPG
Чтобы установить GPG на компьютере, функционирующем под управлением Linux,
необходимо выгрузить соответствующий архивный файл из сайта www. gnupg. org.
В зависимости от того, был ли выбран архив . tar. gz или . tar. bz2. придется исполь-
зовать утилиты gunzip и tar, либо извлечь файлы из архива.
Для компиляции и установки программы используются те же команды, что и для
большинства Linux-программ:
configure (или . /configure, в зависимости от настроек системы)
make
make install
Если инсталляция производится непривилегированным пользователем, следует
запустить сценарий конфигурирования с опцией --prefix:
./configure --pref1х=/путь/к/вашему/каталогу
Это связано с тем, что только привилегированный пользователь обладает правами
доступа к каталогу по умолчанию, где размещается GPG.
Если все прошло гладко, то код GPG будет скомпилирован и исполняемый файл
будет скопирован в каталог /usr/local/bin/gpg либо указанный каталог. Многие
опции можно изменить — более подробная информация приведена в документации
по GPG.
Для Windows-сервера процесс выглядит еще проще. Требуется загрузить zip-файл,
разархивировать его и поместить файл gpg. ехе в каталог, указанный в переменной
PATH. (Вполне подойдет каталог С: \Windows\ или что-то похожее.) Затем потребуется
создать каталог С: \gnupg и ввести команду gpg в командной строке.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
405
Кроме того, следует установить GPG или PGP и сгенерировать пару ключей в сис-
теме, с которой будет поступать почта.
На Web-сервере практически отсутствуют различия между версиями командной
строки GPG и PGP, поэтому вполне можно использоваться пакет GPG, поскольку он
распространяется свободно. Однако на компьютере, с которого будет поступать поч-
та, лучше отдать предпочтение коммерческой версии PGP, чтобы воспользоваться
удобным графическим интерфейсом модуля, встраиваемого в систему чтения почты.
Если на компьютере, который будет выполнять чтение почты, еще нет пары клю-
чей, ее необходимо создать. Вспомните, что пара ключей состоит из открытого клю-
ча (public key), который другие пользователи (и PHP-сценарии) применяют для
шифрования почты до отправки вам, и закрытого ключа (private key), который
используется с вашей стороны для дешифрации полученных сообщений или для под-
писи исходящих сообщений.
Важно, чтобы генерация ключа производилась на компьютере, где будет произво-
диться чтение почты, а не на Web-сервере, так как закрытый ключ не должен хра-
ниться на Web-сервере.
Если для генерации ключей применяется версия командной строки GPG, следует
ввести такую команду:
gpg —gen-key
Программа задаст несколько вопросов, причем на большинство из них можно дать
ответ, предлагаемый по умолчанию. Программа запросит имя, адрес электронной
почты и комментарий, которые будут использоваться в имени ключа. (Ключ автора
называется 'Luke Welling <luke@tangledweb.com.au>'. ) Шаблон создания ключей
очевиден.
Для экспорта общедоступного ключа из вновь созданной пары можно воспользо-
ваться командой
gpg —export > имя_файла
В результате будет создан бинарный файл, подходящий для импорта GPG- или
PGP-ключей на другой компьютер. Если ключ требуется переслать по электронной
почте, его можно сохранить в ASCII-формате с помощью следующей команды:
gpg —export -а > имя_файла
После извлечения открытого ключа файл можно загрузить в свою учетную запись
на Web-сервере. Это можно сделать с использованием протокола FTP.
Далее подразумевается, что на сервере установлена операционная система UNIX.
Для Windows шаги будут такими же, однако имена каталогов и системных команд бу-
дут другими.
Необходимо зарегистрироваться под своей учетной записью на Web-сервере и из-
менить права доступа к файлу, чтобы его могли читать другие пользователи:
chmod 644 имя_файла
Потребуется создать набор ключей, чтобы пользователь, под именем которого за-
пускаются PHP-сценарии, мог использовать программу GPG. Каким является этот
пользователь, зависит от того, как настроен сервер. Зачастую это пользователь no-
body, но возможны и другие варианты.
406
Часть III. Электронная коммерция и безопасность
Итак, следует зарегистрироваться в качестве пользователя Web-сервера. Для этого
необходимо располагать правами доступа к серверу привилегированного пользовате-
ля. Во многих системах Web-сервер запускается пользователем nobody. Это предпола-
гается и в последующих примерах. (Можно изменить имя так, как того требует ваша
система.) Введите следующие команды:
su root
su nobody
Создайте каталог для пользователя nobody для сохранения в нем набора ключей и
другой конфигурационной информации, связанной с GPG. Каталог должен распола-
гаться в домашнем каталоге пользователя nobody.
Домашний каталог каждого пользователя определен в файле /etc/passwd. Во
многих Linux-системах домашним каталогом пользователя nobody по умолчанию яв-
ляется корневой каталог /, причем nobody не обладает правами записи в него. Во
многих BSD-системах домашний каталог пользователя nobody по умолчанию опреде-
лен как /nonexistent. Поскольку’ этого каталога не существует, в него нельзя ничего
записать. В нашем случае пользователю nobody в качестве домашнего установлен ка-
талог /tmp. Вам нужно будет убедиться, что пользователь Web-сервера имеет такой
домашний каталог, в который он может выполнять запись.
Введите:
cd -
mkdir .gnupg
Пользователю nobody потребуется собственный ключ для подписи. Для его созда-
ния следует еще раз запустить команду:
gpg --gen-key
Поскольку, скорее всего, пользователь nobody получает очень мало сообщений
электронной почты, для него можно создать только ключ для подписи исходящей
почты. Его единственное назначение — сделать заслуживающим доверия извлечен-
ный ранее открытый ключ.
Для импорта этого открытого ключа применяется следующая команда:
gpg --import имя_файла
Чтобы сообщить GPG, что этому ключу можно доверять, необходимо изменить
свойства ключа:
gpg --edit-key 'Luke Welling <luke@tangledweb.com.au>'
Текст в кавычках представляет собой имя ключа. Очевидно, что именем вашего
ключа будет не 'Luke Welling <luke@tangledweb.com.au>', а комбинация имени,
комментария и адреса электронной почты, заданных при его генерации.
Среди опций этой программы имеется и help, которая выводит описание доступ-
ных команд — trust (доверять), sign (подписать) и save (сохранить).
Опция trust указывает GPG, что ключ достоин полного доверия, sign применя-
ется для подписи открытого ключа с использованием закрытого ключа пользователя
nobody, save — для выхода из программы с сохранением всех изменений.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
407
Тестирование GPG
После всех этих операций пакет GPG настроена и готова к использованию.
Для ее тестирования потребуется создать файл test. txt, содержащий некоторый
текст.
Запуск команды:
gpg -а —recipient 'Luke Welling <luke@tangledweb.com.au>' —encrypt test.txt
(где задано имя вашего ключа) должен привести к выдаче следующего предупрежде-
ния:
gpg: Warning: using insecure memory!
gpg: Предупреждение: используется незащищенная память!
и создать файл с именем test. txt. asc. Он содержит зашифрованное сообщение,
которое выглядит подобно показанному ниже:
----BEGIN PGP MESSAGE-----
Version: GnuPG vl.0.3 (GNU/Linux)
Comment: For info see http://www.gnupg.org
hQEOAODU7hVGgdtnEAQAhr4HgR7xpIBsK9CiELQw85+klQdQ+p/FzqL8tICrQ+B3
0GJTEehPUDErwqUw/uQLTds0rloPSrIAZ7c6GVkhQYEVBj2MskT81IIBvdo95OyH
K9PUCvg/rLxJlkxe4Vp8QFET5E3FdII/ly8VP5gSTE7gAgm0SbFf3S91PqwMyTkD
/2oJEvL6e3cP384sOi81rBbDbOUAAhCjJXt2DX/uX9q6P18QW56UICUOn4DPaWlG
/gnNZCkcVDgLcKfBjbkB/TCWWhpA7o7kX4CIcIh7KlIMHY4RKdnCWQf271oE+8i9
cJRSCMsFIoI6MMNRCQHY6p9bfxL2uE39IRJrQbe6xoEe0nkB0uTYxiL0TG+FrNrE
tvBVMS0nsHu7HJey+oY4Z833pk5+MeVwYumJwlvHjdZxZmV6wz46GO2XGT17b28V
wSBnWOoBHSZsPvkQXHTOq65EixP8y+YJvBN3z4pzdH0Xa+NpqbH7q3+xXmd3OhDR
+u716MxTLDbgC+NR
=gfQu
----END PGP MESSAGE-----
При переносе этого файла на систему, где был создан ключ, и запуске команды:
gpg -d test.txt.asc
вы должны снова быть в состоянии прочесть исходный текст сообщения. Текст будет
записан в файл с тем же именем, что и ранее — в данном примере это test. txt.
Для вывода текста на экран служит флаг -d:
gpg -d test.txt.asc
Для сохранения текста в файл с именем, отличным от принятого по умолчанию,
потребуется указать флаг -d и задать соответствующее имя файла:
gpg -do test.out test.txt.asc
Обратите внимание, что сначала указывается имя выходного файла.
Если GPG настроен так, что пользователь, от имени которого запускаются РНР-
сценарии, может запустить его из командной строки, то практически все завершено.
Если что-то не работает, проконсультируйтесь с системным администратором либо
обратитесь к документации по GPG.
В листингах 17.1 и 17.2 показано, как можно пересылать зашифрованные почто-
вые сообщения, используя РНР для вызова GPG.
408
Часть III. Электронная коммерция и безопасность
Листинг 17.1. private_mail .php — HTML-форма для отправки зашифрованных
почтовых сообщений
<html>
<body>
<М>Отправь мне приватное сообщение</Ь1>
<?php
// Эту строку необходимо изменить, если не используются порты
// по умолчанию (порт 80 для обычного трафика и порт 443 для SSL)
if($_SERVER['SERVER_PORT‘]!=443)
echo '«pxfont color = "red">
ПРЕДУПРЕЖДЕНИЕ: вы подключились к этой странице не через SSL.
Ваше сообщение может быть прочитано другими.</fontx/p>';
>>
«form method = "post" action = "send_private_mail.php"xbr />
Ваш адрес электронной почты:<br />
<input type = "text" name = “from" size = "38"xbr />
Тема:<Ьг>
cinput type = "text" name = "title” size = "38”xbr />
Ваше сообщение:cbr />
<textarea name = "body” cols = ”30" rows = "10">
</textareaxbr />
cinput type = "submit” value = "Отправить">
</form>
</body>
</html>
Листинг 17.2. send_private_mail.php — PHP-сценарий для вызова GPG и отправки
зашифрованной почты
<?php
// Создание коротких имен переменных
$from = $_POST['from'];
$title = $_POST['title'];
$body - $_POST['body'];
$to_email = 'luke@localhost';
// Укажите gpg, где находится набор ключей.
// В данной системе домашним каталогом пользователя nobody является /tmp/
putenv('GNUPGHOME=/tmp/.gnupg');
// Создание уникального имени файла
$infile = tempnam('', 'pgp’);
$outfile = $infile.'.asc';
// Запись в файл текста, введенного пользователем
$fp = fopen($infile, 'w');
fwrite($fp, $body);
fclose($fp);
// Настройка параметров команды
Scommand = ”/usr/local/bin/gpg -a \\
--recipient 'Luke Welling <luke@tangledweb.com.au>' \\
--encrypt -o $outfile $infile";
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
409
// Выполнение команды gpg
system($command, $result) ;
// Удаление незашифрованного временного файла
unlink($infile);
if($result==0)
{
$fp = fopen($outfile, 'r');
if(!$fp||filesize ($outfile)==0)
{
Sresult = -1;
}
else
{
// Чтение зашифрованного файла
$contents = fread ($fp, filesize ($outfile));
// Удаление зашифрованного временного файла
unlink($outfile);
mail($to_email, $title, $contents, "From: Sfrom\n");
echo ' <Ь1>Сообщение отправлено</М>
<р>Ваше сообщение зашифровано и отправлено.</р>
<р>Спасибо.'<р>';
if($result!=0)
{
echo '<hl>Error:</hl>
<р>Ваше сообщение не может быть зашифровано,
поэтому оно не отправлялось.</р>
<р>Извините.</р?';
Чтобы этот код работал в вашей ситуации, необходимо внести некоторые измене-
ния, Сообщение электронной почты отправляется по адресу $to_email.
Приведенную ниже строку в листинге 17.2:
putenvf'GNUPGHOME=/tmp/.gnupg');
следует изменить так, чтобы она отражала местонахождение набора ключей GPG.
В системе автора Web-сервер запускается от имени пользователя nobody, начальным
каталогом которого является / tmp/.
Функция tempnam () используется для создания уникального имени временного
файла. Можно указать и каталог, и префикс имени файла. Поскольку подобные фай-
лы создаются и удаляются в течение буквально секунды, их имена не имеют особого
значения. В данном случае мы указали префикс ' рдр1, но разрешили РНР использо-
вать системный каталог для хранения временных файлов.
Оператор:
$command = '/usr/local/bin/gpg -а '.
'—recipient 'Luke Welling <luke@tangledweb.com.au>' '.
'—encrypt —о $outfile $infile';
410
Часть III. Электронная коммерция и безопасность
настраивает команду' и ее параметры для вызова GPG. Этот оператор можно изменить в
соответствии со своими потребностями. Как и при использовании командной стро-
ки, GPG необходимо указать, какой ключ применять для шифрования сообщений.
Оператор:
system($command, Sresult);
выполняет инструкции, записанные в строке $ command, и присваивает возвращаемое
значение переменной $result.
Возвращаемое значение, в принципе, можно опустить, но оно позволяет исполь-
зовать условный оператор if и сообщить пользователю, если что-то будет выполнено
неудачно.
Когда временные файлы становятся ненужными, они удаляются с помощью функ-
ции unlink (). Это значит, что пользовательская почта в незашифрованном виде хра-
нится на сервере в течение достаточно короткого промежутка времени. Более того,
если Web-сервер выйдет из строя во время выполнения, файл останется на сервере.
При обсуждении вопросов безопасности сценариев важно принять во внимание
все информационные потоки внутри системы. GPG позволяет отправителю шифро-
вать почту, а получателю — дешифровать ее, но как именно информация поступает от
отправителя к получателю? Если для отправки зашифрованной с помощью GPG поч-
ты используется Web-интерфейс, поток информации будет выглядеть примерно так,
как показано на рис. 17.5.
Рис. 17.5. В нашем приложении обработки зашифрованных сообщений элек-
тронной почты сообщение трижды пересылается через Internet
На этом рисунке каждая стрелка представляет пересылку' сообщения с одного
компьютера на другой. При каждой пересылке сообщение путешествует через Inter-
net, проходя по пути промежуточные сети и компьютеры.
Рассматриваемый здесь сценарий хранится на компьютере, обозначенном на диа-
грамме как Web-cepeep. На нем сообщение шифруется с использованием от крытого клю-
ча получателя. После этого за счет использования SMTP-протокола сообщение пересы-
лается на почтовый сервер получателя. Получатель соединяется со своим почтовым
сервером по протоколу POP или IMAP и загружает сообщение с помощью почтового
клиента. Здесь он дешифрует сообщение, используя свой закрытый ключ.
Пересылки данных на рис. 17.5 обозначены цифрами 1, 2 и 3. На этапах 2 и 3 ин-
формация пересылается в форме зашифрованного при помощи GPG сообщения и
недоступна тем, кто не обладает закрытым ключом. Однако при пересылке 1 сообще-
ние имеет обычный текстовый вид, в котором отправитель вводил его в HTML-
форму.
Если информация настолько важна, что шифруется на втором и третьем этапах,
нелогично пересылать ее в обычном формате на первом этапе. Именно поэтому сце-
нарий размещается на сервере, поддерживающем SSL.
Глава 17. Реализация безопасных транзакций с помощью РНР и MySQL
411
Если соединение со сценарием происходит без SSL, выдается предупреждение.
Это проверяется путем анализа переменной $_SERVER [' SERVER_PORT' ]. SSL-соеди-
нения по умолчанию по умолчанию поступают на порт 443. Любые другие соедине-
ния приводят к ошибке.
Вместо того чтобы выдавать сообщение об ошибке, можно обработать эту ситуа-
цию по-другому. Можно просто перенаправить пользователя на тот же Internet-адрес,
но через SSL-соединение. Кроме того, ошибку можно просто игнорировать, посколь-
ку зачастую не важно, была ли форма доставлена через безопасное соединение. Важ-
но только, чтобы информация, введенная пользователем в форму, пересылалась по
безопасному соединению. Поэтому можно просто задать полный Internet-адрес в па-
раметре action HTML-дескриптора form.
Сейчас открывающий HTML-дескриптор form выглядит следующим образом:
<form method = "post” action = "send_private_mail.php">
Его можно изменить так, чтобы данные пересылались серверу через SSL-соеди-
нение, даже если пользователь подключился без SSL:
<form method = "post" action = "https://Web-cepBep/send_private_mail.php”>
Если полный Internet-адрес жестко закодирован подобным образом, можно быть
уверенным, что пользовательские данные будут передаваться через SSL, однако в
этом случае код придется изменять при каждом использовании на другом сервере или
даже просто в другом каталоге.
Хотя в данном случае, как и во многих других, не столь уж важно, чтобы пустая
форма пересылалась пользователям через SSL-соединение, обычно лучше сделать
именно так. Если пользователь будет видеть символ замка в строке состояния своего
браузера, он будет уверен, что передача информации будет выполняться безопасным
образом. Далеко не все пользователи будут просматривать исходный HTML-код, что-
бы узнать, какие именно действия в плане защиты указаны в атрибутах формы.
Дополнительные источники информации
Описание спецификации SSL 3.0 можно найти на сайте Netscape по адресу:
http://home.netscape.сот/eng/ss!3/
Дополнительные сведения о сетях и сетевых протоколах можно найти в классиче-
ской книге Дугласа Камера Сети TCP/IP, том 1. Принципы, протоколы и структуры, 4-е
издание (Издательский дом “Вильямс”, 2003).
Что дальше
На этом обсуждение основных вопросов создания сайтов электронной коммерции
и достижения приемлемого уровня безопасности завершается. В следующей части
книги рассматриваются некоторые более сложные технологи РНР, в том числе,
взаимодействие с другими компьютерами в Internet, динамическая генерация изо-
бражений и управление сеансами.
412
Часть III. Электронная коммерция и безопасность
Усовершенствованные
технологии РНР
Глава 18.
Глава 19.
Глава 20.
Глава 21.
Глава 22.
Глава 23.
Взаимодействие с файловой системой и сервером
Использование функций работы с сетью и протоколами
Работа с датой и временем
Генерация изображений
Управление сеансами в РНР
Другие полезные свойства
18
Взаимодействие
с файловой системой
и сервером
В главе 2 мы узнали, как читать данные из файлов и записывать данные в файлы
на Web-сервере. В данной главе мы ознакомимся с другими РНР-функциями,
позволяющими взаимодействовать с файловой системой Web-сервера.
В главе, помимо прочих, рассматриваются следующие темы:
Загрузка файлов на сервер с помощью РНР.
Использование функций работы с каталогами.
Работа с файлами на сервере.
Запуск программ на сервере.
Использование переменных среды сервера.
Рассмотрим пример, проясняющий использование этих функций.
Вообразим ситуацию, когда клиенту нужно предоставить возможность изменять
часть содержимого Web-сайта — например, последние новости об их компании. (Или.
может быть, вам хочется иметь для себя интерфейс более дружественный, чем пред-
лагаемый FTP или SCP.) Один из способов сделать это состоит в разрешении клиенту
загружать текстовые файлы с информацией. Затем эти файлы будут доступны на сай-
те через шаблон, разработанный на РНР, как это было сделано в главе 6.
Прежде чем окунуться в системные функции работы с файлами, давайте кратко
ознакомимся с тем, как происходит загрузка файла на сервер.
Загрузка файлов
В РНР доступна очень полезная функциональность — поддержка загрузки файлов
через HTTP-протокол. Вместо того чтобы принимать файлы через HTTP-протокол с
сервера в браузер, мы пересылаем их в обратном направлении, то есть с браузера на
сервер. Обычно для этого применяются HTML-формы. Форма, которая будет исполь-
зована в нашем примере, показана на рис. 18.1.
414
Часть IV. Усовершенствованные технологии РНР
Рис. 18.1. HTML-форма, используемая для загрузки файлов на
сервер, содержит поля и типы полей, отличные от применяе-
мых в обычных формах HTML
Как видно на рисунке, форма содержит поле ввода, в котором пользователь может
ввести имя файла, или щелкнуть на кнопке “Обзор”, чтобы выбрать файлы, доступ-
ные на своей локальной машине. Возможно, вы ранее не видели форму загрузки фай-
лов. Ниже мы покажем, как ее реализовать.
После ввода имени файла пользователь может щелкнуть на кнопке “Послать
файл”, и файл загрузится на сервер, где его ождидает РНР-сценарий.
HTML-код для загрузки файла
Для реализации загрузки файла на сервер применяются некоторые конструкции
языка HTML, специально предназначенные для этой цели. HTML-код для нашей
формы показан в листинге 18.1.
Листинг 18.1. upload.html — HTML-форма для загрузки файлов
<html>
<head>
< Ь^1е>Администрирование - загрузка новых файлов</дШе>
</head>
<body>
<М>Загрузка новых файлов с новостями</Ы>
<form enctype= "multipart/form-data" action="upload.php" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000">
Загрузить файл: <input name="userfile" type="file">
<input type=”submit" value="Послать файл">
</form>
</body>
</html>
Обратите внимание, что в этой форме используется метод POST. Загрузку файла
можно осуществить и с помощью метода PUT, поддерживаемого инструментами
Глава 18. Взаимодействие с файловой системой и сервером
415
Netscape Composer и Amaya, однако при этом придется внести существенные измене-
ния в код. Упомянутые инструменты не поддерживают метод GET.
Рассмотрим особенности этой формы.
В дескрипторе <form> необходимо установить атрибут enctype= "multipart/
form-data" для извещения сервера, что вместе с обычной информацией фор-
мы посылается и файл.
Форма должна содержать поле, в котором задан максимальный размер загру-
жаемого файла. Это скрытое поле, и оно представлено с помощью HTML-
дескриптора:
cinput type="hidden" name="MAX_FILE_SIZE" value="1000000”>
Именем этого поля формы должно быть MAX_FILE_SIZE. Его значение — это мак-
симальный размер (в байтах) файлов, разрешенный для загрузки. Здесь он уста-
новлен в 1 000 000 байт (около 1 Мбайт). В вашем приложении можете сделать
его большим или меньшим.
В форме должно присутствовать поле ввода с типом file, у нас оно задано сле-
дующим образом:
<input name=”userfile" type="file">
Имя поля ввода файла можно выбрать любое, надо лишь помнить его, так как это
имя будет использовано в принимающем PHP-сценарии для доступа к файлу.
Замечания по поводу безопасности
Прежде чем двигаться дальше, стоит напомнить, что у некоторых версий РНР в ко-
де загрузки файла присутствую! бреши в безопасности. Если вы решите пользоваться
загрузкой файлов на свой рабочий сервер, нужно удостовериться, что v вас установлена
самая последняя версия РНР, и следить за выходом правок и обновлений.
Это не должно стать причиной отказа от такой полезной технологии, но при на-
писании кода нужно проявлять осторожность и стараться ограничивать доступ всем,
кроме, скажем, администраторов сайта и менеджеров содержимого.
Написание PHP-сценария для работы с файлами
PHP-код загрузки файла очень прост, однако он зависит от используемой версии
РНР и настроек конфигурации. Имена функций и переменных изменяются от версии
к версии и зависят от того, включена ли настройка register_globals. Представлен-
ный ниже код не требует register_globals. но требует использования версии РНР
не ниже 4.1.
Файл при загрузке помещается в место, отведенное на Web-сервере для времен-
ных файлов. По умолчанию это главный временный каталог Web-сервера. Если файл
не переименовать и не переместить до окончания выполнения сценария, он будет
уничтожен.
Данные, которые должны обрабатываться в нашем PHP-сценарии, хранятся в су-
перглобальном массиве $_FILES. Если register_globals включен, к данным возмо-
жен и непосредственный доступ через имена переменных. Однако здесь, пожалуй,
как раз то место, где лучше отключить register_globals и работать с данными через
суперглобальный массив.
416
Часть IV. Усовершенствованные технологии РНР
Элементы в массиве $_FILES будут сохранены с именем дескриптора <file> из
вашей HTML-формы. Поскольку элемент формы имеет имя userfile, содержимое
массива $_FILES выглядит следующим образом:
Значение, хранимое в $_FILES [' user file' ] [' tmp_name' ], представляет собой
место временного хранения файла на Web-сервере.
Значение, хранимое в $_FILES[ 'userfile' ] [ 'name' ], является именем файла
в системе пользователя.
Значение, хранимое в $_FILES [' userfile' ] [ 1 size' ], указывает размер файла
в байтах.
Значение, хранимое в $_FILES [' userf ile' ][' type'], содержит MIME-тип
файла, например, text/plain или image/gif.
Значение, хранимое в $_FILES [' userf ile'] [' error1 ], будет содержать код
ошибки, возникшей во время загрузки файла. Эта функциональность появи-
лась в версии РНР 4.2.0.
Теперь, когда известно, где находится файл и как он называется, можно скопиро-
вать его в более полезное место. Временный файл по окончании выполнения сцена-
рия будет удален. Значит, если требуется сохранить файл, его надо переместить или
переименовать.
В нашем примере предполагается, что загруженные файлы представляют со-
бой статьи с новостями, поэтому нужно удалить из них все возможные HTML-
дескрипторы и перенести в более подходящий каталог. Сценарий, выполняющий
это, показан в листинге 18.2.
Листинг 18.2. upload.php — PHP-сценарий приема файла от HTML-формы
<html>
<head>
<title>3arpy3Ka...</title>
</head>
<body>
<Ь1>Загрузка файла...</hl>
<?php
if ($_FILES['userfile']['error'] > 0)
{
echo 'Проблема: ';
switch ($_FILES['userfile']['error'])
{
case 1: echo 'размер файла больше upload_max_filesize'; break;
case 2: echo 'размер файла больше max_file_size’; break;
case 3: echo 'загружена только часть файла'; break;
case 4: echo 'файл не загружен'; break;
}
exit ;
}
// Проверка, имеет ли файл правильный М1МЕ-тип?
if ($_FILES['userfile']['type'] != 'text/plain')
{
Глава 18. Взаимодействие с файловой системой и сервером
417
echo 'Проблема: файл не является текстовым';
exit;
}
// помещаем файл туда, куда нужно
Supfile = '/uploads/'.$_FILES['userfile']['name'j;
if ($_FILES['userfile']['tmp_name'])
{
if (!move_uploaded_file($_FILES['userfile']['tmp_name'’, Supfile))
{
echo 'Проблема: невозможно переместить файл в каталог назначения';
exit;
}
}
else
{
echo 'Проблема: возможна атака через загрузку файла. Файл: ';
echo $_FILES['userfile']['name'];
exit;
}
echo 'Файл успешно загружен.<br /><Ьг />';
// переформатирование содержимого файла
$fp = fopen($upfile, 'г');
$contents = fread($fp, filesize (Supfile));
fclose ($fp);
Scontents = strip_tags(Scontents);
$fp = fopen(Supfile, 'w');
fwrite($fp, Scontents);
fclose($fp);
// вывод загруженного файла
echo 'Предварительный просмотр содержимого загруженного файла:<br /><hr />';
echo Scontents;
echo '<br /><hr />';
?>
</body>
</html>
Интересно, что большую часть сценария составляют проверки на предмет воз-
никновения ошибок. Загрузка файла сопряжена с потенциальным риском нарушения
безопасности, и этот риск должен быть сведен к минимуму. Нужно как можно более
тщательно проверить файл, дабы убедиться в безопасности его отображения посети-
телю.
Посмотрим, какие основные части содержит сценарий.
Сначала проверяется код ошибки, возвращаемый в $_FILES [ ' userf ile' ] [' error ';.
Этот код ошибки был введен в РНР 4.2.0. Начиная с РНР 4.3, каждому коду ошибки
соответствует специальная константа. Возможные константы и их значения перечис-
лены ниже:
UPLOAD_ERROR_OK — равна 0, означает, что ошибок не было.
418
Часть IV. Усовершенствованные технологии РНР
UPLOAD_ERR_INI_SIZE — равна 1, означает, что размер загруженного файла
превышает максимальное значение, заданное в файле php. ini директивой up-
load_max_filesize.
UPLOAD_ERR_FORM„SIZE — равна 2, означает, что размер загруженного файла
превышает максимальное значение, заданное в HTML-форме элементом
MAX_FILE_SIZE.
UPLOAD—ERR_PARTIAL — равна 3, означает, что загружена только часть файла.
UPLOAD_ERR_NO_FILE — равна 4, означает, что файл не загружен.
Если вы хотите использовать более старую версию РНР, поместите в код провер-
ки, описанные в руководстве по РНР соответствующей версии.
Далее мы проверяем MIME-тип. В данном случае мы решили, что будем загру-
жать только текстовые файлы, поэтому MIME-тип контролируется путем сравне-
ния $_FILES['userfile']['type'] со строкой 'text/plain'. Это только проверка
на предмет ошибки, а не проверка, связанная с безопасностью. MIME-тип определя-
ется браузером пользователя на основе расширения файла и затем передается
серверу. Поскольку достаточно несложно передать ложный MIME-тип, зло-
умышленники вполне могут воспользоваться этим.
Затем мы проверяем, что файл действительно загружен и не является локальным
файлом вроде /etc/passwd. Несколько позже мы еще вернемся к этому’ вопросу.
Если все нормально, то мы копируем файл в предназначенный для него каталог. В
данном примере это каталог /uploads/ — он находится за пределами дерева Web-
документов и поэтому удобен для помещения в него тех файлов, которые впоследст-
вии будут куда-нибудь включаться.
Затем мы открываем файл, удаляем из него все случайные HTML- и РНР-
дескрипторы с помощью функции strip_tags () и записываем файл обратно.
И, наконец, содержимое файла отображается на экране, чтобы пользователь убе-
дился, что загрузка файла успешно завершена.
Результат (успешного) выполнения сценария можно видеть на рис. 18.2.
В сентябре 2000 года появилось сообщение о способе, позволяющем взломщику
заставить сценарий загрузки файла обработать локальный файл вместо загружен-
ного. Этот способ зафиксирован в списке рассылки BUGTRAQ (Имеется в виду сайт
www.bugtraq.com, посвященный проблемам компьютерной безопасности. Сущест-
вует несколько аналогичных рассылок типа NTBugTraq и VulnWatch; доступен так-
же русскоязычный вариант BUGTRAQ по адресу www.bugtraq.ru — прим. ред.).
Официальные рекомендации по безопасности можно прочитать во многих архивах
BUGTRAQ, таких как http: / /lists. insecure.org/bugtraq/2000/Sep/0237 .html.
Чтобы не иметь дело с брешью подобного рода, в сценарии использовались функ-
ции is_uploaded_f ile () и move_uploaded_f ile (), чтобы удостовериться, что обра-
батываемый файл действительно загружен и не является локальным наподобие
/etc/passwd. Эти функции стали доступны в РНР, начиная с версии 4.0.3.
Если отнестись к написанию сценария, управляющего загрузкой файла, небрежно,
посетитель-злоумышленник может подставить свое собственное временное имя фай-
ла и заставить ваш сценарий обработать этот файл как загруженный. Поскольку мно-
гие сценарии загрузки файлов отображают пользователю загруженные данные или
сохраняют их где-то для последующей загрузки на сайт, это может дать возможность
Глава 18. Взаимодействие с файловой системой и сервером
419
обратиться к любому файлу, доступному для чтения Web-сервером. В числе этих фай-
лов могут находиться и очень важные данные, подобные файлу /etc/passwd или
файлам с исходным кодом на РНР, которые содержат ваши пароли доступа к базам
данных.
Рис. 18.2. После копирования и переформатирования загру-
женного файла его содержимое отображается на экране в качест-
ве подтверждения пользователю, что загрузка успешно завершена
Часто встречающиеся проблемы
При загрузке файлов на сервер следует помнить о нескольких моментах.
В предыдущем примере предполагалось, что где-то имеется список зарегист-
рированных пользователей. Нельзя позволять кому угодно загружать файлы на
ваш сайт.
Если вы все же позволяете ненадежным или незарегистрированным пользова-
телям загружать файлы, то стоит очень внимательно проверять их содержимое.
Вряд ли вы захотите, чтобы на ваш сервер был загружен и выполнен хакерский
сценарий. Нужно проявлять осторожность не только по отношению к типу и
содержимому файла, как это было продемонстрировано, но и к самому имени
файла. Неплохо переименовывать загруженные файлы так. чтобы имя было
гарантированно “безопасным”.
Если ваша машина работает под управлением Windows, то везде в пути к файлу
вместо \ нужно использовать \\ или /.
Использование имен файлов, введенных пользователями, как это было в рас-
смотренном сценарии, чревато возникновением разнообразных проблем.
Наиболее очевидная из них связана с риском случайной перезаписи сущест-
вующих файлов при совпадении имен. Менее очевидная проблема состоит в
том, что различные операционные системы и даже разные языковые настрой-
ки разрешают использовать различающиеся наборы символов в именах фай-
420
Часть IV. Усовершенствованные технологии РНР
лов. Загружаемый файл может иметь имя, которое содержит символы, недо-
пустимые в вашей системе.
Если при попытке загрузки файла на сервер возникают проблемы, проверьте
файл php. ini. Нужно, чтобы в директиве upload_tmp_dir был указан каталог, к
которому у вас имеется доступ. Если вам нужно загружать большие файлы, то,
возможно, следует изменить и директиву memory_l im.it — она определяет мак-
симальное количество байтов в файле, которое вы можете загрузить. В Apache
присутствуют еще и конфигурируемые времена тайм-аута и пределы размера
транзакции, на которые также следует обратить внимание в случае возникно-
вения проблем.
Использование функций работы
с каталогами
После того, как пользователи загрузили нужные файлы, было бы удобно дать им
возможность просматривать загруженные файлы и работать с содержимым тексто-
вых файлов. В РНР реализован набор функций для работы с файлами и каталогами, с
помощью которых и решаются задачи подобного рода.
Чтение содержимого каталога
Первый сценарий, который мы реализуем, предназначен для просмотра содер-
жимого каталога с загруженными файлами. В листинге 18.3 показан простой сцена-
рий, используемый для достижения данной цели.
Листинг 18.3. browsedir .php — вывод содержимого каталога с загруженными файлами
<html>
<head>
<title>ripocMOTp каталогов</11Ъ1е>
</head>
<body>
<Ы>Просмотр</Ь1>
<?php
$current_dir = '/uploads/';
$dir = opendir($current_dir) ;
echo ”<р>Каталог загрузки: $current_dir</p>";
echo '<p>Cодержимое каталога:</p><ul>';
while ($file = readdir($dir))
{
echo "<li>$file</li>";
}
echo '</ul>';
closedir($dir) ;
</body>
</html>
В данном сценарии используются функции opendir (), closedir () и readdir ().
Глава 18. Взаимодействие с файловой системой и сервером
421
Функция opendir () открывает каталог для чтения. Ее применение аналогично
функции открытия файла fopen (), только вместо имени файла нужно передать имя
каталога:
$dir = opendir ($current_dir),-
Эта функция возвращает дескриптор каталога, опять-таки аналогично тому, как
функция fopen () возвращает дескриптор файла.
После открытия каталога можно прочитать имя файла с помощью вызова
readdir ($dir), как показано в нашем примере. Если в каталоге больше нет файлов,
эта функция возвращает false. (Однако она возвращает false и в том случае, если
прочитано имя файла "О" — конечно, если такой случай возможен, потребуется пре-
дусмотреть соответствующую проверку.) Файлы никак не сортируются, так что если
нужен отсортированный список, надо будет прочитать их в массив и отсортировать
его перед отображением на экране.
После завершения работы с каталогом нужно вызвать функцию closedir ($dir),
чтобы закрыть его. Это тоже похоже на вызов функции fclose!), работающей с
файлами.
Пример вывода результатов выполнения сценария просмотра каталога показан на
рис. 18.3.
Если вы хотите применить этот механизм для просмотра каталогов, разумно огра-
ничить список доступных для просмотра каталогов, чтобы пользователь не смог про-
сматривать каталоги, обычно недоступные для него.
Иногда бывает полезной функция rewinddir ($dir), которая возвращает чтение
имен файлов на начало каталога.
Вместо этих функций можно использовать класс dir, предоставляемый РНР.
В нем имеются свойства handle и path и методы read (), close () и rewind (), выпол-
няющие те же действия, что и их необъектные аналоги.
Рис. 18.3. Список файлов в каталоге показывает все файлы в выб-
ранном каталоге, в том числе и . (текущий каталог) и . . (родитель-
ский каталог). В принципе, их можно и не отображать
422
Часть IV. Усовершенствованные технологии РНР
Получение информации о текущем каталоге
Если имеется путь к файлу, то можно получить некоторые дополнительные све-
дения.
Функции dirname (Spath) и basename (Spath) возвращают части пути, содержа-
щие, соответственно, каталог и имя файла. Эта информация может пригодиться в
нашей программе просмотра каталога, особенно в тех случаях, когда требуется соз-
дать сложную структуру каталогов, основанную на осмысленных именах каталогов и
файлов.
С помощью функции disk_free_space (Spath) в список содержимого каталога
можно также включить индикацию свободного места для загружаемых файлов. Если
передать этой функции путь к каталогу, она выдаст количество байтов, свободных на
диске (в Windows) или в файловой системе (в Unix), в которой находится каталог.
Создание и удаление каталогов
Кроме пассивного чтения информации о каталогах, можно создавать и удалять ка-
талоги с помощью PHP-функций mkdir () и rmdir (). Создавать и уничтожать каталоги
можно только в тех путях, к которым разрешен доступ пользователю, выполняющему
сценарий.
Использование функции mkdir () не так просто, как может показаться на первый
взгляд. Она принимает два параметра: путь к желаемому каталогу’ (включая и имя созда-
ваемого каталога) и права доступа, которые вы хотите назначить каталогу, например:
mkdir("/trnp/testing", 0777);
Однако права доступа, указанные вами, не обязательно станут результирующими.
Инвертированное значение текущей маски umask будет скомбинировано с помощью
операции AND, в результате чего получится реальные права доступа. Например, если
umask равна 022, то получатся права доступа 0755.
Чтобы учесть данный эффект, перед созданием каталога можно сбросить маску
umask с помощью следующего кода:
Soldmask = umask(O);
mkdir("/tmp/testing”, 0777);
umask(Soldmask);
В этом коде задействована функция umask (), которая используется и для получе-
ния значения, и для изменения текущей маски umask. Она заменяет текущее значение
umask на переданный ей параметр и возвращает старое значение umask, а если вы-
звать ее без параметров, то просто возвращает значение текущей маски доступа.
Понятно, что в системе Windows функция umask () не выполняет никаких действий.
Функция rmdir () удаляет каталог следующим образом:
rmdir("/tmp/testing") ;
или
rmdir( "с: WtmpWtesting") ;
Удаляемый каталог должен быть пуст.
Глава 18. Взаимодействие с файловой системой и сервером
423
Взаимодействие с файловой системой
Можно не только просматривать и получать информацию о каталогах, но и взаи-
модействовать с Web-сервером и получать информацию о файлах, хранящихся на
нем. Мы уже рассматривали запись файлов и их чтение. Однако существует и множе-
ство других полезных функций.
Получение информации о файле
Можно изменить ту часть сценария просмотра каталога, которая читает файлы,
следующим образом:
while ($file = readdir($dir))
{
echo '<a href="filedetails.php?file='.$file$file.'</a><br />';
}
Затем потребуется создать сценарий filedetails.php, выдающий дополнитель-
ную информацию о файле. Содержимое этого сценария представлено в листинге
18.4.
Предупреждение по поводу этого сценария: некоторые используемые здесь функ-
ции не поддерживаются системой Windows (или поддерживаются не так, как следует);
в их числе функции posix_getpwuid (), fileowner () и f ilegroup ().
Листинг 18.4. filedetails .php — функции работы со статусом файлов и их результаты
<html>
<head>
<Ь1Ь1е>Информация о $afine</title>
</head>
<body>
<?php
$current_dir = '/uploads/';
$file = basename($file);//удаление информации о каталоге для большей безопасности
echo '<Ь1>Информация о файле: '.$file.'</hl>';
$file = $current_dir.$file;
echo '<Ь2>Данные о файле</Ь2>';
echo 'Последнее обращение: '.datel'j F Y H:i', fileatime($file)).'<br />';
echo 'Последняя модификация: '.datel'j F Y H:i', filemtime($file)).'<br />';
$user = posix_getpwuid(fileowner($file));
echo 'Владелец файла: ',$user['name'].'<br />';
$group = posix_getgrid(filegroup($file));
echo 'Группа файла: '.$group['name'].'<br />';
echo 'Права доступа: '.decoct(fileperms($file)).'<br />';
echo 'Тип файла: '.filetype($file).'<br />';
echo 'Размер файла: '.filesize($file).' байтов<Ьг /> ';
echo '<Ь2>Статус файла</Ь2>';
echo 'Каталог: '.(is_dir($file)? 'да' : 'нет').'<Ьг
424
Часть IV. Усовершенствованные технологии РНР
echo 'Исполняемый: 1.(is_executable($file)? 'да' : 'нет').'<Ьг />';
echo 'Файл: '.(is_file($file)? 'да' : 'нет').'<Ьг />';
echo 'Ссылка: '.(is_link($file)? 'да' : 'нет').'<Ьг />';
echo 'Разрешено чтение: '.(is_readable($file)? 'да' : 'нет').'<Ьг />';
echo 'Разрешена запись: '.(is_writable($file)? 'да' : 'нет').'<Ьг />';
</body>
</html>
Пример результата выполнения сценария из листинга 18.4 показан на рис. 18.4.
Рис. 18.4. Окно просмотра информации о файле отображает
сведения о файле, выдаваемые файловой системой. Права дос-
тупа представлены в восьмеричном формате
Давайте разберемся, что выполняет каждая функция, которая используется в лис-
тинге 18.4. Как упоминалось ранее, функция basename () возвращает имя файла без
каталога. (Можно также использовать функцию dirname (), чтобы получить имя ката-
лога без имени файла.)
Функции f ileatime () и f ilemtime () возвращают метку времени, соответственно,
последнего обращения к файлу и его последней модификации. Для наглядности мет-
ки времени были отформатированы с помощью функции date (). Для некоторых
операционных систем эти функции могут давать одно и то же значение (как в нашем
примере) — это зависит от того, какую информацию сохраняет система.
Функции fileowner() и filegroupO возвращают идентификатор пользователя
(uid) и идентификатор группы (gid) файла. Они могут быть преобразованы в имена с
помощью функций posix_getpwuid () и posix_getgrgid(), соответственно, после
чего воспринимаются несколько лучше. Эти функции в качестве параметра прини-
мают uid или gid и возвращают ассоциативный массив с информацией о пользовате-
Глава 18. Взаимодействие с файловой системой и сервером
425
ле или группе, в том числе имя пользователя или группы, что и было выполнено в
нашем сценарии.
Функция fileperms () возвращает права доступа к файлу. В примере они пере-
форматированы с помощью функции decoctf) в восьмеричный вид, более привыч-
ный для пользователей UNIX-систем.
Функция filetype () возвращает информацию о типе анализируемого файла. Ее
возможными результатами являются: fifo, char. dir. block, link, file и unknown.
Функция filesize () возвращает размер файла в байтах.
Функции из следующего набора — is_dir(), is_executable(), is_file(),
is_link (), is_readable () и is_writable () — тестируют соответствующие атрибуты
файла и возвращают true или false.
Вместо них аналогичную информацию можно получить с помощью функции
stat (). По заданному файлу она возвращает массив, содержащий данные, аналогич-
ные результатам вышеприведенных функций. Похожим образом работает и функция
Is tat (), но она применяется для символьных ссылок.
Все функции работы со статусом файла требуют серьезных затрат времени для
своего выполнения, поэтому результаты их работы кэшируются. Если нужно полу-
чить некоторую информацию о файле до изменения и после, необходимо с помощью
следующей функции:
clearstatcache();
очистить предыдущие результаты. Для использования последнего сценария до и по-
сле изменения некоторой информации о файле нужно в начало сценария вставить
вызов этой функции, чтобы гарантировать получение самых свежих данных.
Изменение свойств файла
Можно не только просматривать свойства файла, но и изменять их.
Каждая из функций chgrp(file, group), chmod(file, permissions) и
chown(file, user) работает аналогично своему эквиваленту' в операционной систе-
ме Unix. Ни одна из них не работает’ в Windows, хотя функция chown () выполняется и
всегда возвращает true.
Функция chgrp () применяется для изменения группы, имеющей доступ к файлу.
Она может изменить группу’ лишь на одну' из тех, членами которых является пользо-
ватель, если только это не привилегированный пользователь loot.
Функция chmod () изменяет права доступа к файлу. Права доступа, передаваемые
ей в качестве параметра, записываются в обычной для Unix-команды chmod форме:
чтобы показать, что они записаны в восьмеричной норме, спереди надо приписать
’ 0", например:
chmod(’somefile.txt', 0777);
Функция chown () изменяет владельца файла. Ее можно применять, только если
сценарий выполняется с правами root, чего происходить не должно, если только вы
не запускаете сценарий из командной строки для выполнения задач администриро-
вания.
426
Часть IV. Усовершенствованные технологии РНР
Создание, удаление и перемещение файлов
С помощью функций файловой системы файлы можно создавать, перемещать и
удалять.
Первое, и самое простое, что можно сделать — это создать файл или изменить
время его последней модификации, используя для этого функцию touch (). Она рабо-
тает аналогично Unix-команде touch. Прототип функции имеет следующий вид:
inc touch (string file, [int time [, int atime]])
Если такой файл уже существует, время его последней модификации будет изме-
нено либо на текущее время, либо на время, заданное вторым параметром, если он
присутствует. Если нужно задать это время, оно должно быть задано в формате метки
времени. Если файл не существует, он будет создан. Время последнего обращения к
файлу также будет изменено: по умолчанию — на текущее системное время либо на
метку времени, если задан необязательный параметр atime.
Удалить файл можно с помощью функции unlink (). (Заметьте, что эта функция
не называется delete — “удалить”, такой функции попросту нет.) Используется она
следующим образом:
unlink($filename);
Это одна из функций, которые не работают в некоторых старых версиях Windows.
Но в этом случае в Windows можно удалить файл с помощью
system(" del fi1 ename.ext") ;
Копировать и пересылать файлы можно с помощью функций сору () и rename (),
как показано ниже:
copy($source_path, $destination_path);
rename($oldfile, $newfile) ;
В листинге 18.2 использовалась функция сору ().
Функция rename () имеет двойное назначение: она еще и перемещает файлы из
одного места в другое, поскольку в РНР нет специальной функции перемещения.
Возможность перемещения файлов из одной файловой системы в другую и перезапи-
си поверх старых файлов при использовании функции rename () зависит от операци-
онной системы, так что необходимо ознакомиться с возможностями вашего сервера.
Также надо соблюдать осторожность при использовании пути в имени файла. Если
он относительный, то относительно местонахождения сценария, а не самого файла.
Использование функций запуска программ
С функциями, работающими с файловой системой, мы закончили, и теперь обра-
тимся к функциям, с помощью которых можно запускать программы на сервере.
Они могут оказаться полезными при создании Web-интерфейса в системе, взаи-
модействующей с пользователем через командную строку. Такие команды были ис-
пользованы, например, для создания интерфейса диспетчера почтовой рассылки
ezmlm. Мы воспользуемся ими снова несколько позже, когда дойдем до исследования
учебных примеров.
Глава 18. Взаимодействие с фвйловой системой и сервером
427
Существуют четыре основных способа выполнения команд на Web-сервере. Все
они очень похожи, тем не менее, им присущи и небольшие различия.
1. ехес ().
Функция ехес () имеет следующий прототип:
string ехес (string command [, array result [, int return_value]])
В качестве аргумента передается командная строка, которую нужно выполнить,
например:
ехес("Is -la");
У функции ехес () нет непосредственных выходных данных.
Она возвращает последнюю строку результата выполнения команды.
Если передать ей в качестве параметра переменную result, то после выполне-
ния в ней будет содержаться массив всех строк результата выполнения команды.
Если передать еще и переменную return_code, в нее будет помещен код возврата.
2. passthru().
Функция passthru () имеет следующий прототип:
void passthru (string command [, int return_value])
Функция passthru () просто передает свои выходные данные в браузер. (Это
может оказаться полезным в случае бинарных данных, например, изображения в
каком-нибудь формате.)
Никакого значения не возвращается.
Параметры функции означают то же, что и в функции ехес ().
3. system().
Функция system () имеет следующий прототип:
string system (string command [, int return_value])
Данная функция передает в браузер выходные данные команды. Она пытается
передать выходные данные каждой строки (если РНР выполняется как сервер-
ный модуль), что отличает ее от функции passthru ().
Она возвращает последнюю строку выходных данных (при успешном выполне-
нии) или false (при неудаче).
Смысл параметров такой же, как и для других функций.
4. Обратные кавычки.
Они уже кратко были упомянуты в главе 1. Фактически это оператор выполне-
ния команды.
У них нет непосредственных выходных данных. Результат выполнения команды
возвращается в виде строки, которую затем можно отобразить на экране либо
использовать как-то по-другому.
Если у вас более сложные запросы, то можно также воспользоваться функциями
popen(), ргос_ореп() и proc_close (), которые предназначены для создания внеш-
них процессов и передачи данных по каналам к ним и от них. Последние две из этих
функций появились в версии РНР 4.3.
428
Часть IV. Усовершенствованные технологии РНР
Сценарий, показанный в листинге 18.5, содержит эквивалентные примеры ис-
пользования каждого из этих четырех способов.
Листинг 18.5. progex.php — функции работы со статусом файла и их результаты
<?php
chdir(’/uploads/');
11111 версия exec
echo '<pre>';
// unix
exec('Is -la', $result);
// windows
// exec('dir', $result);
foreach ($result as $line)
echo "$line\n”;
echo '</pre>';
echo '<br /><hr /xbr />’;
11111 версия passthru
echo '<pre>';
// unix
passthru('Is -la');
// windows
// passthru('dir');
echo '</pre>';
echo '<br /><hr /xbr />';
Illi I версия system
echo '<pre>';
// unix
$result = system!'Is -la');
// windows
// $result = system)'dir') ;
echo '</pre>';
echo ' <br / xhr / xbr / > ’ ;
///// версия с обратными кавычками
echo '<pre>’;
// unix
$result = 'Is -al';
// windows
// $result = 'dir';
echo $result;
echo '</pre>';
Один из рассмотренных способов может применяться вместо сценария просмот-
ра каталога, приведенного ранее. Обратите внимание, что в этом коде хорошо виден
один из побочных эффектов использования внешних функций: код не является пере-
носимым. Здесь вызывались команды Unix, и ясно, что данный код не сможет отра-
ботать в среде Windows.
Если в текст команды, которую нужно выполнить, требуется включить данные,
предоставляемые пользователем, ее необходимо всегда сначала пропускать через
Глава 18. Взаимодействие с файловой системой и сервером
429
функцию escapeshellcmdl). Это не позволит пользователям преднамеренно (или
нечаянно) выполнять команды в вашей системе. Вот пример вызова этой функции:
system(escapeshellcmd($command));
Можно также воспользоваться функцией escapeshellarg () для отмены всеъ ар-
гументов, которые нужно передать команде оболочки.
Взаимодействие с окружением:
функции getenv () и putenv ()
В завершение данного раздела мы рассмотрим, как в РНР можно использовать пе-
ременные окружения. Для этой цели существуют две функции: getenv (), позволяю-
щая получить переменные окружения, и putenvf), позволяющая устанавливать зна-
чения этих переменных.
Обратите внимание, что окружение, о котором идет здесь речь — это среда на сер-
вере, в которой выполняется РНР-сценарий.
Список всех переменных окружения для РНР можно получить с помощью вызова
функции phpinfо (). Некоторые из этих переменных более полезны, другие — менее,
например:
getenv("HTTPJREFERER");
возвратит URL-адрес страницы, с которой пользователь пришел на текущую страницу.
Можно также устанавливать требуемые значения переменных среды с помощью
функции putenv (), например:
Shome = "/home/nobody";
putenv ('' HOME=Shome ");
Если вы — системный администратор и хотите ограничить список переменных
окружения, которые доступны для переустановки пользователям, можно воспользо-
ваться директивой safe_mode_allowed_env_vars в файле php. ini. При работе РНР в
безопасном режиме пользователи смогут изменять только те переменные окружения,
префиксы которых перечислены в этой директиве.
Дополнительную информацию о том, что представляют собой переменные окру-
жения, можно получить в спецификации CGI по адресу:
http://hoohoo.ncsa.uiuc.edu/cgiIenv.html
Дополнительные источники информации
Большинство PHP-функций, работающих с файловой системой, отображаются
на соответствующие функции операционной системы: если вы работаете в UNIX,
за дополнительной информацией обращайтесь к тап-страницам.
Что дальше
В главе 19 будет показано, как с помощью PHP-функций работы с сетью и прото-
колами взаимодействовать с системами, отличающимися от системы на вашем Web-
сервере. Это еще более расширит возможности разрабатываемых сценариев.
430
Часть IV. Усовершенствованные технологии РНР
19
Использование функций
работы с сетью
и протоколами
В данной главе мы рассмотрим сетевые PHP-функции, позволяющие сценариям
взаимодействовать с миром Internet. В нем имеется огромное количество ресур-
сов и множество протоколов для доступа к ним.
В главе, помимо прочих, рассматриваются следующие темы:
Обзор доступных протоколов.
Отправка и получение почты.
Использование других Web-сайтов через HTTP.
Применение функций сетевого контроля.
Использование FTP.
Обзор сетевых протоколов
Протоколы — это правила общения в конкретных ситуациях. Например, хорошо из-
вестен протокол встречи со знакомым: вы здороваетесь, пожимаете руку, разговари-
ваете и затем прощаетесь. Различные ситуации требуют соблюдения различных прото-
колов. Человек другой культуры может ожидать другой манеры общения, что зачастую
весьма затрудняет взаимодействие. Аналогично устроены и сетевые протоколы.
Как и ритуалы общения между людьми, разные компьютерные протоколы приме-
няются в различных ситуациях и приложениях. Для передачи и приема Web-страниц
используется протокол HTTP, или протокол передачи гипертекста (Hypertext Trans-
fer Protocol). Возможно, вы также знакомы с протоколом FTP — протоколом передачи
файлов (File Transfer Protocol), применяемым для пересылки файлов по сети между
компьютерами. Существует и множество других протоколов.
Протоколы и другие стандарты Internet описаны в документах, называемых RFC
(Requests For Comments — Запросы на комментарии). Эти стандарты составляются
организацией Internet Engineering Task Force (IETF). Документы RFC широко рас-
пространены в Internet. Основным их источником является Web-сайт редактора RFC
(RFC Editor): http: //www.rfc-editor.org/.
Если при работе с каким-либо протоколом возникают трудности, документы RFC
могут послужить надежным источником информации, полезным при отладке кода.
Однако эти документы очень подробны и часто содержат сотни страниц.
Наиболее известными документами являются, например, RFC2616, содержащий
описание протокола HTTP/1.1, и RFC822, в котором описан формат почтовых сооб-
щений Internet.
В данной главе рассматриваются аспекты РНР, использующие некоторые из этих
протоколов. Мы обсудим отправку почтовых сообщений с применением протокола
SMTP, получение почты с помощью POP и IMAP, соединение с Web-серверами по
HTTP и HTTPS, а также пересылку файлов по FTP.
Отправка и получение почты
Основной способ отправки почтовых сообщений в РНР заключается в простом
вызове функции mail (). Ее применение обсуждалось в главе 4, поэтому здесь мы не
будем к ней возвращаться. Эта функция использует для отправки почты протокол
SMTP (Simple Mail Transfer Protocol — Простой протокол пересылки почты).
Для повышения функциональности mai 1 () можно воспользоваться одним из
множества свободно распространяемых классов. В главе 28 используется класс рас-
ширения, позволяющий отправлять HTML-файлы, прикрепленные к почтовому со-
общению. Протокол SMTP предназначен только для отправки почты. А для получе-
ния почты с почтового сервера используются протоколы IMAP (Internet Message
Access Protocol — Протокол для доступа к сообщениям Internet, описанный в
RFC2060) и POP (Post Office Protocol — Почтовый протокол, описанный в RFC1939 и
STD0053). Эти протоколы не предназначены для отправки сообщений.
IMAP применяется для получения и управления почтовыми сообщениями, храни-
мыми на сервере, и является более сложным, чем POP, основное применение кото-
рого заключается в простой загрузке сообщений на клиентскую машину и удалении
их с сервера.
РНР содержит библиотеку IMAP. Ею можно воспользоваться для установки не
только IMAP-соединений, но и соединений POP и NNTP (Network News Transfer Pro-
tocol — Протокол передачи сетевых новостей).
Подробно использование библиотеки IMAP будет рассматриваться в проекте, ко-
торый описывается в главе 27.
Использование других Web-сайтов
Одно из лучших применений Web заключается в возможности использовать, из-
менять или встраивать существующие службы и информацию в собственные страни-
цы. РНР существенно облегчает эти действия, и сейчас мы увидим это на примере.
Предположим, что компании, в которой вы работаете, требуется отображать на
своей домашней странице котировки собственных акций. Эта информация доступна
в Web на сайте некоторой фондовой биржи, но как ее получить?
Прежде всего, необходимо определить URL-адрес, по которому размещена нужная
информация. После этого всякий раз, когда кто-либо заходит на домашнюю страницу
432
Часть IV. Усовершенствованные технологии РНР
вашей компании, можно открыть соединение с этим URL, получить страницу и из-
влечь из нее соответствующую информацию.
В качестве примера напишем сценарий, который получает и форматирует бирже-
вую информацию NASDAQ (Система автоматической котировки Национальной ассо-
циации биржевых дилеров), публикуемую на сайте АМЕХ. Берется пример котировки
акций Internet-магазина Amazon.com. (Информация, необходимая для вашей страницы,
может отличаться, однако принцип тот же.)
Эта технология известна под названием “вырезания из экрана", поскольку вы берете
информацию, изначально предназначенную для отображения на экране, и изымаете из
нее те части, которые нужны для представления с помощью нового интерфейса. Сце-
нарий приведен в листинге 19.1.
Листинг 19.1. lookup.php — сценарий, запрашивающий информацию NASDAQ о
котировке акций компании, обозначение которой передается в переменной $syinbol
<html>
<head>
<title>Ko™poBKa акций от NASDAQ</title>
</head>
<body>
<?
// Выбор обозначения компании
$symbol='AMZN';
echo "<Ы>Котировка акций $symbol</hl>";
$theurl =
"http: //www.amex.com/equities/listCmp"
."/EqLCDetQuote.j sp?Product_Symbol=$symbol";
if (!($contents = file_get_contents($theurl)))
{
echo 'Невозможно открыть URL';
exit;
1
// Найти нужную часть страницы и отобразить ее
Spattern = "(\\$[0-9 ]+\\.[0-9]+) ";
if (eregi($pattern, Scontents, $quote))
{
echo "<р>Последняя цена продажи $symbol:
echo $quote[l];
echo '<p>';
} else
{
echo 'Котировка недоступна';
};
// Указать источник информации
echo '<br />'
.'Информация получена с сайта <br />'
."<а href=\"$theurl\">$theurl</axbr />"
. (dated jS F Y g:i a T'));
?>
</body>
Глава 19. Использование функций работы с сетью и протоколами
433
</html>
Пример выполнения сценария 19.1 показан на рис. 19.1.
Рис. 19.1. Регулярное выражение в сценарии используется для получе-
ния биржевой котировки из информации, взятой с фондовой биржи
Сам сценарий достаточно прост — фактически все функции, используемые в нем,
уже рассмотрены нами ранее, хотя здесь они применяются по-другому.
Действительно, в главе 2, при обсуждении чтения из файлов, упоминалось, что
эти функции можно использовать для чтения из URL-адреса. Именно так и происхо-
дит в данном примере. Следующий вызов функции f ile_get_contents ():
$contents = file_get_contents($theurl)
возвращает полный текст страницы по заданному URL-адресу, который затем сохра-
няется в переменной $ contents.
С помощью файловых функций в РНР можно сделать очень многое. Представлен-
ный пример кода просто загружает Web-страницу по HTTP, тем не менее, совершен-
но аналогично можно еще и взаимодействовать с внешними серверами через HTTPS,
FTP и другие протоколы. Для решения некоторых задач может потребоваться более
специализированные подходы. Часть функциональности FTP доступна только в спе-
цифических функциях FTP, и не доступна через fopen () и другие файловые функции.
Позже в этой главе мы рассмотрим пример использования функций FTP. При реше-
нии определенных задач, связанных с HTTP и HTTPS, может потребоваться библио-
тека cURL. С помощью этой библиотеки выполняется вход на Web-сайт и имитация
прохода пользователем по нескольким страницам.
Получив с использованием функции file_get_contents () в переменной $соп-
tents полный текст Web-страницы, можно выполнить поиск требуемой части стра-
ницы через регулярное выражение и функцию eregi ():
$pattern = “(\\$[0-9 ]+\\.[0-9]+)";
if (eregi($pattern, $contents, $guote))
{
echo "<р>Последняя цена продажи $symbol:
echo $guote[l];
434
Часть IV. Усовершенствованные технологии PHP
echo ’<p>';
} else
Вот и все!
Представленный подход можно применять для достижения многих целей. Другим
примером может послужить извлечение информации о погоде в каком-нибудь месте и
вставка ее на свою страницу.
Комбинируя с помощью этого подхода информацию из различных источников,
можно придать странице еще большую ценность. Забавным примером на эту тему яв-
ляется известный сценарий Филиппа Гринспана (Philip Greenspun) под названием
Bill Gates Wealth Clock (Счетчик финансового состояния Билла Гейтса); он доступен
по адресу http: / ,/philip. greenspun. com/WealthClock.
Эта страница имеет два источника информации. Из сайта U.S. Census Bureau (Бю-
ро переписи населения США) запрашивается численность населения США. Затем
запрашивается текущий курс акций Microsoft, эти данные объединяются, добавляется
изрядная часть мнения самого автора и вычисляется новая информация — приблизи-
тельная оценка финансового состояния Билла Гейтса. (Похоже, что этот сайт пере-
стал работать; причина, как на нем упоминается, состоит в том, что “что-то пошло не
так, как планировалось”. Возможно, сайт и заработает, возможно нет. — прим. ped.).
Хотя это и не связано с программированием, но если вы используете чужую ин-
формацию в коммерческих целях, как в этом примере, стоит вначале внимательно
изучить источник. В некоторых случаях потребуется рассмотреть вопросы, связан-
ные с охраной прав на интеллектуальную собственность.
Если создается сценарий наподобие показанного выше, может возникнуть необ-
ходимость в передаче данных, например, параметров, введенных пользователем, при
соединении с внешним URL. В этом случае может помочь функция url_encode ().
Она преобразует заданную строку' в формат, более подходящий для URL, заменяя,
например, пробелы знаками плюс. Вызов функции выглядит так:
$encodedparameter = url_encode($parameter),-
Рассматриваемый подход может породить и трудности, если на сайте, с которого
вы берете информацию, изменится формат данных: это приведет к прекращению
работы вашего сценария.
Более удобным способом являются Web-службы (Web Services). Они похожи на
удаленные объекты, к которым можно подключиться и получить нужные данные,
вроде котировок акций. Поддержка Web-служб в РНР постоянно растет. Преимуще-
ства применения Web-служб связаны с тем, что вы используете четко определенный
интерфейс и функции, которые поставщик Web-служб явно или неявно открыл
внешнему миру.
Применение функций сетевого контроля
РНР содержит набор функций “контроля”, предназначенных для проверки ин-
формации об именах хостов, IP-адресах и почтовых обменах. Например, если созда-
ется справочный сайт, подобный Yahoo!, перед добавлением новых URL-адресов
можно автоматически проверить правильность информации о хосте и контактной
информации. Поступая таким образом, можно сэкономить пользователям немало
Глава 19. Использование функций работы с сетью и протоколами
435
усилий, чтобы в дальнейшем не оказалось, что сайт не существует или почтовый ад-
рес некорректен.
Пример HTML-кода для ввода информации в справочный сайт представлен в лис-
тинге 19.2.
Листинг 19.2. directory submit.html — HTML-код для формы ввода информации
<head>
<title>OinpaBbTe свой cafti</title>
</head>
<body>
<М>Отправьте сайт</Ь1>
<form method="post" action="directory_submit.php">
URL: cinput type="text" name="url" size="30" value=''http://”><br />
Адрес электронной почты: <input type=”text" name="email" size=”23" xbr />
cinput type="submit" value=”Отправить">
</form>
</body>
</html>
Это очень простая форма; вместе с введенными данными она показана на рис. 19.2.
Рис. 19.2. В формах ввода информации на справочных сайтах
обычно требуется URL-адрес сайта и контактная информация,
чтобы администраторы могли послать извещение о добавлении
сайта в поисковую систему
После щелчка на кнопке Отправить в первую очередь необходимо убедиться, что,
во-первых, URL-адрес размещен на реальной машине, и, во-вторых, что хост почто-
вого адреса также реален. Ниже приводится сценарий, выполняющий эти действия, а
результаты его работы показаны на рис. 19.3.
Сценарий, предназначенный для выполнения этих проверок, использует две
функции из набора сетевых PHP-функций: gethostbyname() и dns_get_mx(). В лис-
тинге 19.3 представлен полный исходный код сценария.
436
Часть IV. Усовершенствованные технологии РНР
В Pply льтаты передачи сайга - Mo?iUa {Bifid 10: 200'4112206}
Jal*!
Правка gtu Пдреход. Закладки ^ктрументы Qkho Справка ОеЦд ЙЛ j
oJL *' Д
Результаты передачи сайта
IP-адрес хоста. 194 183 176 58
Почтовый сервер dial-gw umv kiev ua
Переданные данные корректны.
Спасибо за информацию о вашем сайте
Скоро он будет просмотрен нашим персоналом
М ' готово,
х
Рис. 19.3. Данная версия сценария выводит результаты провер-
ки имени хоста в URL и почтовом адресе — в окончательной
версии эта информация может и не отображаться, но здесь ин-
терес представляют результаты проверки
Листинг 19.3. directory submit.php — сценарий для проверки URL и почтового адреса
<html>
<head>
<11Ъ1е>Результаты передачи сайта</Ш1е>
</head>
<body>
<М>Результаты передачи сайта</М>
<?php
// Извлечение информации из полей формы
$url = $_REQUEST[’url’];
$host = $_REQUEST[’email’];
Il Проверка URL
$url = parse_url ($url)
$host = $url[’host’];
if(!($ip = gethostbyname($host)))
{
echo ’Хост для данного URL не существует’;
exit;
}
echo "IP-адрес хоста: $ip <br />";
// Проверка почтового адреса
$email = explode)$email);
$emailhost = $email[l];
// Внимание! Функция dns_get_mx() ‘не реализована* в Windows-версиях РНР
if (!dns_get_mx($emailhost, $mxhostsarr))
{
Глава 19. Использование функций работы с сетью и протоколами
437
echo 'Хост почтового адреса не существует 1;
exit;
}
echo 'Почтовый сервер: ';
foreach ($mxhostsarr as $mx)
echo "$mx ";
// Если сценарий дошел до этой точки, значит, все в порядке
echo '<br />Переданные данные корректны.<Ьг />';
echo 'Спасибо за информацию о вашем сайте.<Ьг />'
.'Скоро он будет просмотрен нашим персоналом.'
// В реальном случае добавим сайт в базу данных ожидающих сайтов...
</body>
</html>
Рассмотрим наиболее интересные части сценария.
Вначале к заданному URL-адресу применяется функция parse_url (). Эта функция
возвращает ассоциативный массив различных частей имени URL. Доступные порции
информации включают в себя протокол (scheme), пользователя (user), пароль
(pass), хост (host), порт (port), путь (path), запрос (query) и фрагмент (fragment).
Обычно требуются не все эти элементы, но мы сейчас рассмотрим, как из них фор-
мируется URL.
Если задан следующий URL-адрес:
http://nobody:secretSexample.com:80/script.php?variable=value#anchor
то значения каждой части массива будут следующими:
Протокол: http://
Пользователь: nobody
Пароль: secret
Хост: е хатр le.com
Порт: 80
Путь: script .php
Запрос: variable=value
Фрагмент: anchor
В сценарии directory_submit .php требуется только информация о хосте, поэто-
му она извлекается из массива следующим образом:
$url = parse_url($url);
$host = $url['host'];
После этого с помощью функции gethostbyname () можно получить IP-адрес хос-
та, если он известен службе имен доменов (domain name service — DNS). Функция воз-
вращает IP-адрес, если он существует, или false в противном случае:
$ip = gethostbyname($host)
438
Часть IV. Усовершенствованные технологии РНР
Можно и наоборот: функция gethostbyaddr () по IP-адресу хоста возвращает его
символьное имя. Если применить две эти функции одну за другой, то может полу-
читься имя хоста, отличное от исходного. В этом случае сайт, видимо, использует
службу виртуального хостинга, когда одна физическая машина и один IP-адрес обслу-
живает несколько имен доменов.
Если искомый URL является допустимым, мы переходим к проверке почтового
адреса. Вначале он с помощью функции explode () разбивается на имя пользователя и
имя хоста:
$email = explode('@', Semail);
$emailhost = $email[l];
Имея имя хоста, несложно проверить, можно ли на него отправить почту'. Это де-
лается с помощью функции dns_get_mx ():
dns_get_mx($emailhost, Smxhostsarr);
Эта функция возвращает для заданного адреса набор MX-записей (Mail Exchange,
почтовый обмен) в массиве, заданном переменной Smxhostarr.
MX-запись хранится на DNS-серверах, и ее проверка выполняется так же, как про-
верка имени хоста. Машина, указанная в MX-записи, не обязательно является той же,
куда поступит почта. Однако она знает, куда необходимо перенаправить почту. (Ма-
шин может быть несколько, поэтому функция возвращает массив, а не одну строку с
именем хоста.) Если в DNS нет MX-записи для данного хоста, значит, почту туда от-
править невозможно.
Обратите внимание, что функция dns_get_mx() не реализована в Windows-
версиях РНР. Если вы работаете под управлением Windows, то должны пользоваться
пакетом PEAR::Net_DNS, который обеспечит аналогичную функциональность.
Если все проверки завершаются успешно, данные можно разместить в базе данных
для последующего просмотра кем-либо из сотрудников, обслуживающих справочный
сайт.
В дополнение к упомянутым функциям можно использовать более общую функ-
цию checkdnsrr (), которая для имени хоста возвращает значение true, если это имя
присутствует в DNS.
Использование FTP
Протокол File Transfer Protocol (Протокол передачи файлов), или FTP, применя-
ется для пересылки файлов по сети между хостами. С использованием РНР можно
вызывать fopen () и другие файловые функции для FTP-соединений так же, как и для
HTTP-соединений, для установки соединения и передачи файлов на FTP-сервер или
обратно. Кроме того, в стандартной установке РНР имеется набор специализирован-
ных функций для работы с FTP.
Однако эти функции по умолчанию не инсталлируются. Для того чтобы восполь-
'зоваться ими под Unix, необходимо запустить конфигурационную РНР-программу
configure с опцией --enable-ftp, а затем запустить перекомпиляцию с помощью make.
При использовании стандартной установки для Windows FTP-функции инсталли-
руются автоматически.
Более подробно вопросы конфигурировании РНР рассматриваются в приложении А.
Глава 19. Использование функций работы с сетью и протоколами
439
Использование FTP для резервного
и зеркального копирования файла
FTP-функции удобны для пересылки и копирования файлов с других хостов и на
них. Один из распространенных примеров их использования — это создание резерв-
ной или зеркальной копии Web-сайта на другом сервере. В листинге 19.4 в качестве
примера приведен простой сценарий, использующий FTP-функции для создания зер-
кальной копии файла.
Листинг 19.4. ftpmirror. php — сценарий для загрузки новых версий файла из FTP-сервера
<html>
<head>
<Ь1Ь1е>0бновление зеркальной Konnn</title>
</head>
<body>
<М>Обновление зеркальной копии</Ы>
<?php
// Установка переменных — измените их для своих целей
$host = 'ftp.cs.rmit.edu.au';
$user = 'anonymous';
$password = 'me@example.com';
$remotefile = '/pub/tsg/teraterm/ttsshl4 . zip' ;
Slocalfile = 'tmp/writable/ttsshl4.zip';
// Подключение к хосту
Sconn = ftp_connect("$host");
if (!Sconn)
{
echo 'Ошибка: соединение c FTP-сервером невозможно<Ьг />';
exit ;
}
echo "Установлено соединение с $host<br />";
// Регистрация на хосте
@ Sresult = ftp_login(Sconn, $user, Spassword);
if (!Sresult)
{
echo "Ошибка: пользователь $user не зарегистрировансЬг />";
ftp_quit(Sconn);
exit;
}
echo "Начало сеанса пользователя Susercbr />";
11 Проверка времени модификации файла — следует ли его обновлять
echo 'Проверка времени модификации файла...<br />';
if (file_exists(Slocalfile))
{
Slocaltime = filemtime(Slocalfile);
echo 'Последняя модификация локального файла: ';
echo date('G:i j-M-Y', Slocaltime);
echo '<br />';
}
else
$localtime=0;
Sremotetime - ftp_mdtm($conn, Sremotefile);
440
Часть IV. Усовершенствованные технологии PHP
if (!($remotetime >= 0))
{
// Это не значит, что файл не существует,
// сервер может не поддерживать время модификации
echo 'Невозможно получить время модификации удаленного файла.<Ьг />';
$remotetime=$localtime+l; // чтобы обновление выполнилось
}
else
{
echo 'Последняя модификация удаленного файла: ';
echo date('G:i j-M-Y', Sremotetime);
echo '<br />';
}
if (!(Sremotetime > Slocaltime))
{
echo 'Локальная копия актуальна.<br />';
exit;
}
// Загрузка файла
echo 'Чтение файла с сервера...<br />';
$fp = fopen ($localfile, 'w');
if (!$success = ftp_fget($conn, $fp, Sremotefile, FTP_BINARY))
{
echo ‘Ошибка: файл не может быть загружен';
ftp_quit($conn);
exit;
}
fclose($fp);
echo 'Файл успешно загружен';
// Закрытие соединения с хостом
ftp_quit($conn) ;
</body>
</html>
Результат конкретного запуска сценария можно видеть на рис. 19.4.
Сценарий ftpmirror .php является достаточно обобщенным. Он начинается с ус-
тановки переменных:
$host = 'ftp.cs.rmit.edu.au';
$user = 'anonymous';
Spassword = 'me@example.com';
Sremotefile = '/pub/tsg/teraterm/ttsshl4.zip';
$localfile = '/tmp/writable/ttsshl4.zip';
Переменная $host должна содержать имя FTP-сервера, с которым будет происхо-
дить соединение, а переменные $user и Spassword соответствуют имени пользовате-
ля и паролю, необходимым для регистрации на сервере.
Многие FTP-сайты поддерживают так называемый анонимный вход (anonymous
login), то есть общедоступное имя, которым может воспользоваться для регистрации
любой пользователь. Пароль в этом случае не требуется, но правилом хорошего тона
Глава 19. Использование функций работы с свтью и протоколами
441
считается ввод в качестве пароля своего почтового адреса, чтобы системные админи-
страторы могли знать местоположение своих пользователей. Здесь это соглашение
соблюдено.
£3 Обновление зеркальной копии - Mozffia {Btiilrt
Файл фавна Пдэеад Закладки ’ (Инструменты Окно справка Debug ЦА
Назад * Обновить r^ht^^ocalhost^hpmysqt3e/chapterl9/flp_rnirroRf^] Поиск|
Обновление зеркальной копии
Установлено соединение с cs unit edu au
Начало сеанса пользователя anonymous
Проверка времени модификации файла
Последняя модификация локального файла 1342 19-Jun-2005
Последняя модификация удаленного файла 1041 31-Маг-1999
Локальная копия актуальна
Рис. 19.4. Сценарий создания зеркальной копии файла по FTP
проверяет, является ли локальная версия файла актуальной, и
если нет, загружает новую версию
Переменная $remotefile содержит путь к файлу, который требуется загрузить.
В данном случае происходит загрузка и создание зеркальной локальной копии про-
граммы Тега Term SSH — клиента SSH для Windows. (SSH означает “secure shell” — за-
щищенный командный интерпретатор. Это аналог Telnet, в котором при передаче
информации применяется шифрование.)
Переменная $localfile содержит путь, по которому должен быть помещен
загруженный файл на локальной машине. Для данного примера создан каталог
/tmp/writable с правами доступа, достаточными для записи в него файла РНР-
сценарием.
Независимо от используемой операционной системы, этот каталог потребуется
явно создать. Если ваша операционная система поддерживает строгие соглашения по
правам доступа, вы должны убедиться, что сценарий имеет права записи в этот ката-
лог. Чтобы адаптировать сценарий под собственные нужды, необходимо будет соот-
ветствующим образом изменить значения этих переменных.
Основные шаги в этом сценарии совпадают с действиями, предпринимаемыми
при ручной загрузке файла через FTP с использованием интерфейса командной
строки:
1. Подключение к удаленному FTP-серверу.
2. Регистрация (под конкретным именем пользователя или же в качестве
анонима).
3. Проверка, изменен ли удаленный файл.
4. Если да — его загрузка.
5. Закрытие соединения с FTP-сервером.
442
Часть IV. Усовершенствованные технологии РНР
Рассмотрим последовательно каждый шаг.
Подключение к удаленному FTP-серверу
Этот шаг равносилен вводу:
ftp имя_хоста
в командной строке Windows или Unix. В РНР его можно выполнить с помощью сле-
дующего кода:
$сопп = ftp_connect (" $host") ;
if (!$conn)
{
echo 'Ошибка: соединение с FTP-сервером невозможно<Ьг />';
exit;
}
echo "Установлено соединение с $host<br />";
Здесь использована функция f tp_connect (). Функция принимает в качестве па-
раметра имя хоста и возвращает либо дескриптор соединения, либо значение false,
если соединиться не удалось. Вторым, необязательным, параметром функции являет-
ся номер порта подключения на хосте. (Здесь он не используется.) Если номер порта
не указан, по умолчанию соединение осушествляется через порт 21, стандартный
ЕТР-порт.
Регистрация на FTP-сервере
Следующий шаг состоит в регистрации на сервере под именем определенного
пользователя и указанием его пароля. Он достигается с помощью вызова функции
ftp_login():
@ $result = ftp_login($conn, $user, $password);
if (!$result)
{
echo "Ошибка: пользователь $user не зарегистрировансЬг />”;
ftp_quit(Sconn);
exit;
}
echo "Начало сеанса пользователя $user<br />";
Эта функция принимает три параметра: дескриптор FTP-соединения (возвращае-
мый функцией f tp_connect ()), имя пользователя и пароль. При успешной регистра-
ции пользователя функция возвращает значение true, а иначе — false. В начале пер-
вой строки находится символ @, который подавляет вывод Сообщений об ошибках.
Это делается для подавления предупреждений РНР в окне браузера в случае, если
пользователь не сможет зарегистрироваться. Лучше перехватить ошибку, проверив
значение переменной $result, как это сделано в рассматриваемом сценарии, и вы-
дать свое, более дружественное сообщение.
Отметьте, что если попытка регистрации завершилась неудачно, то фактически
FTP-соединение закрывается функцией f tp_quit (), о которой будет рассказано ниже.
Глава 19. Использование функций работы с сетью и протоколами
443
Проверка времени обновления файла
Если требуется обновить локальную копию файла, то логично сначала проверить,
следует ли обновлять файл, поскольку вовсе нет необходимости заново загружать
файл, особенно большой, если он находится в актуальном состоянии. Это позволяет
минимизировать сетевой трафик. Рассмотрим фрагмент кода, который решает эту
задачу.
Учет времен создания и модификации файлов является той причиной, по которой
используются именно FTP-функции, а не более простые файловые функции. С помо-
щью файловых функций можно просто читать, а в ряде случаев и записывать файлы
через сетевые интерфейсы, однако большинство функций получения статуса файлов,
например, filemtimeO, не работают с удаленными файлами. Наверняка в будущем
ситуация изменится. Ввести поддержку ftp:// для файловых функций получения
статуса планируется уже в версии РНР 5.1. Тогда можно будет переписать представ-
ленный сценарий с использованием сору (Sremoteurl, $localurl).
Вначале для определения необходимости загрузки файла с помощью функции
f ile_exists () проверяется наличие локальной копии файла. Если копии нет, то яс-
но, что файл необходимо загрузить. Если же она существует, то при помощи функции
f ilemtime () запрашивается время последней модификации файла, которое присваи-
вается переменной Slocaltime. Если локальной копии файла нет, переменной
Slocaltime присваивается значение 0, чтобы сделать локальный файл заведомо
“старше” любой даты модификации удаленного файла:
echo 'Проверка времени модификации файла...<Ьг />';
if (file_exists(Slocalfile))
{
Slocaltime = filemtime(Slocalfile);
echo 'Последняя модификация локального файла: ';
echo date('G:i j-M-Y', Slocaltime);
echo '<br />';
}
else
$localtime=0;
(О функциях f ile_exists () и filemtime () можно узнать подробнее в главах 2 и 18.)
После выделения времени модификации локального файла необходимо узнать
время модификации файла на удаленной машине. Это можно сделать с помощью
функции f tp_mdtm ():
Sremotetime = ftp_mdtm($conn, Sremotefile);
Эта функция принимает два параметра — дескриптор FTP-соединения и путь к уда-
ленному файлу — и возвращает либо метку времени последней модификации файла в
формате Unix, либо -1, если произошла какая-либо ошибка. Не все FTP-серверы
обеспечивают такую возможность, поэтому вызов этой функции не всегда дает тре-
буемый результат. В этом случае в переменную Sremotetime искусственно заносится
“более новое” время, чем в Slocaltime, то есть увеличенное на 1. Это гарантирует,
что будет предпринята попытка загрузить файл:
if (!(Sremotetime >= 0))
{
// Это не значит, что файл не существует,
444
Часть IV. Усовершенствованные технологии РНР
11 сервер может не поддерживать время модификации
echo 'Невозможно получить время модификации удаленного файла.<br />';
$remotetime=$localtime+l; // чтобы обновление выполнилось
}
else
{
echo 'Последняя модификация удаленного файла: ';
echo date('G:i j-M-Y', $remotetime);
echo '<br />';
)
Теперь, когда имеются даты двух файлов, их можно сравнить и сделать вывод о
необходимости загрузки файла:
if (!($remotetime > Slocaltime))
{
echo 'Локальная копия актуальна.<br />';
exit;
}
Загрузка файла
На этой стадии происходит попытка загрузить файл из сервера:
echo 'Чтение файла с сервера...<br />';
$fp = fopen ($localfile, 'w');
if (!$success = ftp_fget($conn, $fp, $remotefile, FTP_BINARY))
{
echo 'Ошибка: файл не может быть загружен';
ftp_quit($conn);
exit;
)
fclose($ fp);
echo 'Файл успешно загружен';
Как уже должно быть известно, локальный файл открывается с помощью функции
fopen(). После этого вызывается функция ftp_fget(), которая загружает файл и
сохраняет его на локальном диске. Эта функция принимает четыре параметра. На-
значение первых трех из них очевидно: дескриптор FTP-соединения, дескриптор ло-
кального файла и путь к удаленному файлу. Четвертый параметр определяет FTP-
режим.
Пересылка через FTP может осуществляться в двух режимах: ASCII и бинарном.
Режим ASCII используется для пересылки текстовых файлов (то есть файлов, со-
стоящих только из ASCII-символов), а бинарный режим — для пересылки всех осталь-
ных файлов. В бинарном режиме файл передается без какой-либо модификации, то-
гда как в режиме ASCII выполняется трансляция символов возврата каретки и
перевода строки в соответствующие символы, используемые в вашей системе (\п для
Unix, \r\п для Windows и \г для Macintosh).
PHP-библиотека для работы с FTP содержит две предопределенных константы —
FTP_ASCII и FTP_BINARY, соответствующие этим двум режимам. Необходимо выяс-
нить, какой режим более соответствует типу файла и передать соответствующую кон-
станту в четвертом параметре функции ftp_fget(). В данном случае пересылается
zip-файл, поэтому используется режим FTP_BINARY.
Г лава 19. Использование функций работы с сетью и протоколвми
445
Функция f tp_fget () возвращает значение true, если копирование прошло успеш-
но, и false, если возникла ошибка. Результат сохраняется в переменной Ssuccess; он
позволяет сообщить пользователю, как прошла загрузка.
После загрузки локальный файл закрывается с помощью функции f close ().
Вместо ftp_fget() можно было бы воспользоваться альтернативной функцией —
f tp_get (), которая имеет следующий прототип:
int ftp_get (int ftp_connection, string localfile_path,
string remotefile_path, int mode)
Эта функция работает практически так же, как f tp_fget (}, но не требует откры-
тия локального файла. Ей передается имя локального файла, в который будет выпол-
няться запись, а не его дескриптор.
Обратите внимание, что в РНР не существует эквивалента FTP-команды mget, по-
средством которой можно загрузить сразу несколько файлов. Вместо этого нужно
несколько раз вызвать f tp_fget () или ftp_get ().
Закрытие соединения
По окончании необходимо закрыть FTP-соединение с помощью функции
ftp_guit():
ftp_quit($conn);
Этой функции должен быть передан дескриптор FTP-соединения.
Загрузка файлов на сервер
Если же, наоборот, требуется скопировать файлы с локального сервера на удален-
ную машину, для этого используются две функции, противоположные ftp_fget() и
f tp_get (). Они называются f tp_fput () и ftp_put () и имеют следующие прототипы:
int ftp_fput (int ftp_connection, string remotefile_path, int fp, int mode)
int ftp_put (int ftp_connection, string remotefile_path,
string localfile_path, int mode)
Их аргументы совпадают с аргументами своих _деЬ-эквивалентов.
Как избежать тайм-аутов
При передаче файлов через FTP можно столкнуться с проблемой превышения
максимального времени выполнения сценария. Если это происходит, РНР выдает
сообщение об ошибке. Так часто бывает, если локальный сервер подключен к мед-
ленной или перегруженной сети, или когда загружается большой файл, например,
видеоклип.
Максимальное время выполнения по умолчанию для всех сценариев РНР опреде-
лено в файле php. ini. По умолчанию оно составляет 30 секунд. Это сделано для авто-
матического прекращения выполнения сценариев, вышедших из-под контроля. Од-
нако при пересылке файлов через FTP, если у вас медленная связь или очень большой
файл, пересылка может занять больше максимально разрешенного времени.
К счастью, с помощью функции set_time_limit () можно изменить максимальное
время выполнения конкретного сценария. Вызвав эту функцию, можно установить
446
Часть IV. Усовершенствованные технологии РНР
максимальное время для выполнения сценария (в секундах), начиная с момента вы-
зова функции. Например:
set_time_limit(90);
позволит сценарию выполняться еще 90 секунд с момента вызова функции.
Другие функции работы с FTP
В РНР существует и множество других полезных функций работы с FTP.
Функция f tp_size () возвращает размер файла на удаленном сервере и имеет сле-
дующий прототип:
int ftp_size(int ftp_connection, string remotefile_path)
Она возвращает размер удаленного файла в байтах или значение -1 в случае воз-
никновения ошибки. Следует отметить, что не все FTP-серверы поддерживают эту
функцию.
С помощью функции f tp_size () легко оценить максимальное время выполнения
пересылки какого-либо файла. Зная размер файла и скорость соединения, можно
приблизительно оценить время передачи и соответствующим образом воспользо-
ваться функцией set_time_limit ().
Список файлов в каталоге на удаленном FTP-сервере можно получить с помощью
следующего кода:
$listing = ftp_nlist($conn, dirname($remotefile));
foreach ($listing as $filename)
echo "$filename <br />";
Здесь для получения списка имен файлов в конкретном каталоге применяется
функция f tp_n 1 i s t ().
В PHP доступны и другие FTP-функции, которые позволяют выполнить практиче-
ски все, что можно сделать в командной строке FTP-клиента. FTP-функции, соответ-
ствующие командам FTP, можно найти в онлайновом руководстве по РНР, которое
доступно по адресу http://php.net/manual/en/ref.ftp.php.
Исключение составляет команда mget (множественная загрузка), однако можно
воспользоваться функцией ftp_nlist() для получения списка файлов, а затем по-
очередно загрузить их.
Дополнительные источники информации
В этой главе уделялось много внимания основам, но, как несложно догадаться, ма-
териалов, посвященных этим темам, существует гораздо больше.
Информацию об отдельных протоколах и принципах их работы можно найти в
документах RFC по адресу http: / /www.rfc-editor.org/.
Возможно, вам покажется интересной кое-какая информация о протоколах на
сайте консорциума World Wide Web (World Wide Web Consortium):
http://www.w3.org/Protocols/
Глава 19. Использование функций работы с сетью и протоколами
447
Хорошим справочным материалом по семейству протоколов TCP/IP может по-
служить книга Эндрю С. Таненбаума (Andrew S. Tanenbaum) Компьютерные сети, 4-е
издание (Издательский дом “Питер”, 2004).
Что дальше
В главе 20 рассматриваются библиотеки PHP-функций для работы с датой и ка-
лендарем. Вы узнаете, как преобразовывать форматы данных, введенных пользовате-
лем, в форматы РНР и MySQL и обратно.
448
Часть IV. Усовершенствованные технологии РНР
20
Работа с датой
и временем
В этой главе обсуждаются методы проверки и форматирования даты и времени, а
также преобразования даты в различные форматы. Последняя возможность
особенно важна при преобразовании между форматами MySQL и РНР, Unix и РНР, а
также для дат, введенных пользователем в HTML-формах.
В главе, помимо прочих, рассматриваются следующие темы:
Получение даты и времени средствами РНР.
Преобразования дат между форматами РНР и MySQL.
Операции над датами.
Использование календарных функций.
Получение даты и времени средствами РНР
В главе 1 упоминалось об использовании функции date () для получения и форма-
тирования даты и времени в РНР. Ниже мы рассмотрим более подробно эту и другие
PHP-функции, предназначенные для работы со значениями даты и времени.
Использование функции date ()
Функция date () принимает два параметра, один из которых является необяза-
тельным. Первый параметр задает строку формата, а второй, необязательный, — мет-
ку времени Unix. Если метка времени не указана, то по умолчанию функция date()
обрабатывает текущую дату и время. Она возвращает отформатированную строку,
содержащую дату.
Типичный вызов функции выглядит следующим образом:
echo datef'jS F Y');
что в результате дает дату в формате “31th Мау 2005”.
Коды форматирования, воспринимаемые функцией date (), перечислены в
табл. 20.1.
Таблица 20.1. Коды форматирования PHP-функции dated
Код Описание
а Время до или после полудня, представленное двумя строчными буквами: ат или рт.
А Время до или после полудня, представленное двумя прописными буквами: AM или РМ.
В Internet-время Swatch — универсальная временная схема. Более подробно о ней мож-
но узнать на сайте http: / /www. swatch. сот/.
с Дата в соответствие со стандартом ISO 8601. Дата представлена в виде ГПТ-ММ-ДД. Про-
писная буква Т отделяет дату от времени. Время представлено в виде ЧЧ:ММ:СС. Заверша
ет строку часовой пояс, представленный как смещение от среднего времени по Гринвичу
(Greenwich mean time— GMT), например, 2005-06-26Т21:04:42+2:00. (Этот код форма-
та появился в РНР5.)
d День месяца в виде двузнач! юго числа с ведущим нулем. Диапазон значений — от 01 до 31.
D День недели в виде английской трехбуквенной аббревиатуры. Диапазон значений —
от Моп (понедельник) до Sun (воскресенье).
F Полное английское название месяца. Диапазон значений — от January (январь) до
December (декабрь).
g Часы в 12-часовом формате без ведущих нулей. Диапазон значений — от 1 до 12.
G Часы в 24-часовом формате без ведущих нулей. Диапазон значений — от 0 до 2 3.
h Часы в 12-часовом формате с ведущими нулями. Диапазон значений — от 01 до 12.
Н Часы в 24-часовом формате с ведущими нулями. Диапазон значений — от 00 до 23.
1 Минуты с ведущими нулями. Диапазон значений — от 00 до 59.
I Признак применения летнего времени, представленный логическим значением. Если
время летнее, возвращается значение 1, иначе — 0.
1 День месяца в виде числа без ведущих нулей. Диапазон значений — от 1 до 31.
1 Полное английское название дня недели. Диапазон значений — от Monday (понедель-
ник) до Sunday (воскресенье).
L Високосный год, представленный логическим значением. Возвращается значение 1,
если дата принадлежит високосному году, и 0 — в противном случае.
m Номер месяца в двузначном числовом формате с ведущими нулями. Диапазон значе-
ний — от 01 до 12.
М Месяц в виде английской трехбуквенной аббревиатуры. Диапазон значений — от Jan
(январь) до Dec (декабрь).
п Номер месяца в виде числа без ведущих нулей. Диапазон значений — от 1 до 12.
О Разница между текущим часовым поясом и GMT в часах, например, +1600.
г Дата и время в формате, заданном в RFC822, например, Wed, 9 Jun 2005 18:45:30
+1600 (добавлено в РНР 4.0.4).
s Секунды с ведущими нулями. Диапазон значений — от 00 до 59.
S Порядковый суффикс для дат в двухбуквенном формате. Он может принимать значе-
ние st, nd, rd или th в зависимости от числа, за которым следует.
t Полное количество дней в месяце даты. Диапазон значений — от 2 8 до 31.
Т Часовой пояс сервера в трехбуквенном формате, например, EST.
U Число секунд с 1 января 1970 года до текущего момента; его также называют меткой
времени Unix для текущей даты.
450
Часть IV. Усовершенствованные технологии РНР
Окончание табл. 20.1
Код Описание
w Номер дня недели в виде однозначного числа. Диапазон значений — от 0 (воскресе-
нье) до 6 (суббота).
W Номер недели в году в формате ISO-8601 (добавлено в РНР 4.1.0).
У Год в двузначном формате, например, 05.
У Год в четырехзначном формате, например, 2005.
z День года в виде числа. Диапазон значений — от 0 до 3 6 5.
Z Смещение текущего часового пояса в секундах. Диапазон значений — от—43200 до 43200.
Работа с метками времени Unix
Второй параметр функции date () является меткой времени Unix. Для тех, кому
интересно, что это значит: большинство Unix-подобных систем хранят текущее вре-
мя в виде 32-разрядного целого числа секунд, начиная с полуночи 1 января 1970 года
по Гринвичу. Эту дату еще называют началом эпохи Unix. Для непосвященных это
выглядит несколько эзотерично, но таков стандарт, к тому же такие значения легко
обрабатываются компьютером.
Метки времени Unix — компактный способ хранения даты и времени, и стоит от-
метить, что на него совершенно не повлияла проблема 2000-го года (Y2K), от которой
пострадали другие сокращенные форматы хранения даты. Но для программного
обеспечения, которое доживет до 2038 года, возникнет аналогичная проблема. По-
скольку метки времени не имеют своего фиксированного размера, но они жестко
привязаны к размеру длинного целого в языке С (32 бита), то, скорее всего, к 2038
году компиляторы будут использовать более емкий тип. Проблемы будут возникать,
если программное обеспечение имеет дело с датами до 1902 и после 2038 года.
Даже если РНР запускается на Windows-сервере, все равно функция date () и мно-
гие другие PHP-функции используют именно такой формат хранения даты, принятый
для Unix. Одно отличие состоит в том, что в случае Windows значения метки времени
должны быть положительными.
Если требуется преобразовать время и дату в формат метки времени Unix, можно
воспользоваться функцией mktime (), которая имеет следующий прототип:
int mktime ([int hour[, int minute[, int second!, int month[,
int day[, int year [, int is_dst] ]]]]]])
Назначение всех аргументов вполне очевидно, кроме последнего, is_dst, кото-
рый указывает, действует ли переход на летнее время. Его можно установить равным
1, если переход на летнее время действует, 0, если нет, либо -1 (значение по умолча-
нию), если это неизвестно. Этот аргумент является необязательным и поэтому ис-
пользуется редко.
Основная опасность, которой следует избегать при использовании функции
mktime () — достаточно неинтуитивный порядок следования аргументов. Он не по-
зволяет пропустить время. Если время не важно, можно задать часы, минуты и секун-
ды равными 0. Однако можно опустить значения в конце списка параметров. Неза-
данные величины будут взяты из текущего времени. Следовательно, вызов функции:
Глава 20. Работа с датой и временем
451
$timestamp = mktimed;
вернет метку времени Unix для текущей даты и времени. Конечно, тот же результат
можно получить и с помощью такого вызова:
$ times tamp = timed;
Функция time () не принимает параметров и всегда возвращает метку времени
Unix для текущей даты и времени.
Другой возможностью является применение функции date (), как только что упо-
миналось. Строка формата "U" запрашивает метку времени. Показанный ниже опе-
ратор эквивалентный двум предыдущим:
$timestamp = date("U");
В функцию mktime () можно передать год как в двух-, так и в четырехзначном
формате. Двухзначные значения от 0 до 69 интерпретируются как годы от 2000 до
2069, а 70—99 — как годы от 1970 до 1999.
Ниже представлены другие примеры использования функции mktime • i.
$time = mktime(12, 0, 0);
дает полдень для текущей даты.
$time = mktime(0,0,0,1,1);
дает 1 января текущего года.
Функцию mktime () можно также применять для простых арифметических вмчис-
лений над датами. Например:
$time = mktime(12,0,0,$mon,$day+30,$year);
добавляет 30 дней к дате, указанной переданными в функцию компонентами д*же
если ($day+30) обычно будет больше количества дней в месяце.
Дабы избежать некоторых проблем, связанных с летним временем, рекомендуется
использовать вместо 0 часов 12 часов. Если вы добавите (24 * 60 * 60) к полуночи в 25-
часовом дне, вы останетесь в рамках того же дня. Если вы добавите то же значонгс к
полдню, вы получите 11 утра, но, по крайней мере, будете иметь дело с »нр|л iibiii
днем.
Использование функции getdate ()
Другая полезная функция определения даты — функция getdate (). Она имеет еле
дующий прототип:
array getdate ([int timestamp])
Она принимает в качестве необязательного аргумента метку времени tixestMfJ и
возвращает ассоциативный массив, содержащий компоненты этой даты и времени,
как показано в табл. 20.2.
Располагая всеми частями этого массива, вы можете получить из него любой тре
буемый формат представления. Элемент метки времени (0) может показаться излиш-
ним, однако, если вызвать функцию getdate () без параметров, в него будет занесена
текущая метка времени.
452
Часть IV. Усовершенствованные технологии РНР
Таблица 20.2. Ассоциативный массив пар ключ-значение, возвращаемый функцией getdate ()
Ключ Значение
seconds Секунды, числовое значение.
minutes Минуты, числовое значение.
hours Часы, числовое значение.
mday День месяца, числовое значение.
wday День недели, числовое значение.
mon Месяц, числовое значение.
year Год, числовое значение.
yday День года, числовое значение.
weekday День недели, полное английское название.
month Месяц, полное английское название.
0 Метка времени, числовое значение.
Проверка правильности дат
Для проверки правильности дат можно воспользоваться функцией checkdate ().
Это особенно полезно при проверке дат, вводимых пользователем. Функция check-
date () имеет следующий прототип:
int checkdate (int month, int day, int year)
Она проверяет, является ли год целым числом от 0 до 32767, месяц — целым от 1
до 12, и что указанное число существует в заданном месяце. Эта функция учитывает и
високосные годы.
Например:
checkdate(9, 18, 1972);
вернет значение true, а
checkdate(9, 31, 2000)
— значение false.
Преобразования дат между форматами
РНР и MySQL
Дата и время в MySQL поддерживается в формате ISO 8601. Время отображается
практически интуитивно, но ISO 8601 требует указания года первым. Например, дата
31 мая 2005 года может вводиться как 2005-05-31 или 05-05-31. Даты, получаемые из
MySQL, по умолчанию также представлены в этом формате.
В зависимости от потенциальных посетителей, вы можете счесть эту функцию не
особо дружественной к ним. Следовательно, взаимодействие РНР и MySQL обычно
требует некоторого преобразования дат. Такое преобразование можно выполнить на
любой стороне.
Глава 20. Работа с датой и временем
453
При пересылке дат из РНР в MySQL их можно легко преобразовать в требуемый
формат с помощью функции date (), как было показано ранее. Нужно лишь исполь-
зовать версию числа и месяца с ведущими нулями, во избежание путаницы в MySQL.
Вы можете использовать представление года в виде двух цифр, однако представ-
ление в форме четырех цифр — обычно гораздо более хорошая идея. Если же преоб-
разование необходимо выполнить в MySQL, то для этого существуют две полезных
функции: DATE_FORMAT () и UNIX_TIMESTAMP ().
Функция DATE_FORMAT () работает аналогично подобной функции в РНР, но ис-
пользует другие коды формата. Чаще всего она применяется для вывода даты в фор-
мате ММ-ДД-ГПТ (месяц/день/год) вместо естественного для MySQL ISO-формата
ГПТ-ММ-ДД (год/месяц/день). Для этого потребуется сформировать следующий
запрос:
SELECT DATE-FORMAT(date_column, ’%m %d %Y')
FROM tablename;
Код формата %m задает двузначный формат месяца. %d — двузначный формат дня,
%Y — четырехзначный формат года. Наиболее полезные коды формата для преобра-
зования даты в MySQL перечислены в табл. 20.3.
Таблица 20.3. Коды формата MySQL-функции DATE_FORMAT ()
Код Описание
%М Месяц, полное английское название.
%и День недели, полное английское название.
%D День месяца, числовой формат с текстовым суффиксом (например, 1st).
%Y Год, четырехзначное число.
%У Год, двузначное число.
%а День недели, трехсимвольный формат.
%d День месяца, число с ведущими нулями.
%е День месяца, число без ведущих нулей.
%ш Месяц, число с ведущими нулями.
%с Месяц, число без ведущих нулей.
%Ь Месяц, 3-символьное текстовое представление.
%j День года, числовое значение.
%н Часы в 24-часовом формате с ведущими нулями.
%к Часы в 24-часовом формате без ведущих нулей.
%h или %1 Часы в 12-часовом формате с ведущими нулями.
%1 Часы в 12-часовом формате без ведущих нулей.
%i Минуты, число с ведущими нулями.
%г Время в 12-часовом формате (hh:mm:ss [AM | РМ]).
%Т Время в 24-часовом формате (hh:mm:ss).
%S или %s Секунды, число с ведущими нулями.
%р AM или РМ.
%w День недели, число от 0 (воскресенье) до 6 (суббота).
454
Часть IV. Усовершенствованные технологии РНР
Функция UNIX_TIMESTAMP работает аналогично, но преобразует значение столбца
в метку времени Unix. Например:
SELECT UNIX_TIMESTAMP(date_column)
FROM tablename;
возвращает дату в формате метки времени Unix. Затем в РНР с ней можно произво-
дить любые операции.
Метки времени Unix используются для вычислений над датами. Не забывайте, что с
помощью меток времени можно представлять даты только между 1902 и 2038 годами,
тогда как типа даты MySQL допускает гораздо более широкий диапазон.
Операции над датами
Наиболее простой способ вычислить период времени между двумя датами в РНР
связан с расчетом разности между двумя метками времени Unix. Этот подход исполь-
зован в сценарии, приведенном в листинге 20.1.
Листинг 20.1. calc age. php — сценарий для вычисления возраста по дате рождения
<?php
// Определение даты для расчетов
$day = 31;
$month = 5;
$year = 1990;
// Дата рождения требуется в формате день/месяц/год
$bdayunix = mktime (0, 0, 0, $month, $day, $year);
// Получение метки времени Unix для даты рождения
$nowunix = time(); // вычислить метку времени Unix для текущей даты
$ageunix = $nowunix - $bdayunix; // вычислить разность
$age = floor($ageunix / (365 * 24 * 60 * 60)); //преобразование из секунд в годы
echo "Возраст: $аде”;
В этом сценарии дата для подсчета возраста была установлена внутри сценария.
В реальном приложении эта информация обычно поступает из HTML-формы. Сце-
нарий начинается с вызова функции mktime () с целью вычисления меток времени
для даты рождения и текущей даты:
$bdayunix = mktime (0, 0, 0, $month, $day, $year);
$nowunix = time(); // вычислить метку времени Unix для текущей даты
Теперь обе даты имеют одинаковый формат, поэтому можно вычислить их разность:
$ageunix = $nowunix - $bdayunix;
Далее следует более сложный фрагмент кода — обратное преобразование этого
периода времени в более естественные единицы измерения. Это уже не метка време-
ни, а возраст человека в секундах. Преобразовать его в годы можно, разделив на ко-
личество секунд в году. После этого с помощью функции floor () производится ок-
ругление в меньшую сторону, поскольку возраст человека составляет, например, 20
лет только по прошествии двадцатого года от рождения:
$age = floor($ageunix / (365 * 24 * 60 * 60)); //преобразование из секунд в годы
Глава 20. Работа с датой и временем
455
Все же обратите внимание, что этот подход несколько некорректен, так как он ог-
раничивается диапазоном значений меток времени Unix (то есть 32-разряд ними це-
лыми). Пример с днями рождения — совсем не идеальное применение меток времени.
Этот пример нормально работает на всех платформах только для тех, кто родился
после 1970 года. Windows не может управлять метками времени, относящимися до
1970 года. Кроме того, вычисления не всегда точны, поскольку они не учитывают ви-
сокосные года и могут дать сбой, если полночь дня рождения попадает на ночь со-
вершения перехода на дневное время.
Операции над датами в MySQL
Количество встроенных функций манипулирования датами в РНР невелико. По-
нятно, что вы можете реализовать собственные функции, однако принимайте во
внимание, что учет високосных годов и летнего времени достаточно труден. В каче-
стве альтернативы можно загрузить функции, написанные другими разработчиками.
Вы найдете немало замечаний по этому поводу в онлайновом руководстве по РНР,
тем не менее, только некоторые их них заслуживают внимания.
Возможность, которая не сразу возникает как рабочая альтернатива, предусмат-
ривает использование MySQL. MySQL предлагает широкий спектр функций манипу-
лирования датами, которые нормально работают за пределами ограничений, накла-
дываемых на метки времени Unix. Для выполнения MvSQL-запросов вы должны
подключиться к серверу MySQL, однако вовсе не обязательно использовать данные
исключительно из базы данных.
Приведенный ниже запрос добавляет один день к дате 28 февраля 1700 года и воз-
вращает результирующую дату:
select adddate('1700-02-28', interval 1 day)
Поскольку 1700 год не является високосным, результат выглядит как 1700-03-01
(1 марта 1700 года).
Подробное описание синтаксиса для представления и манипуляции датами и вре-
менем можно найти в руководстве по MySQL, которое доступно по адресу:
http://ww.mysql.com/doc/en/Date_and_time_functions.html
К сожалению, не существует простого пути получения числа лет между двумя да-
тами, так что пример с днем рождения все еще актуален. С другой стороны, очень
легко получить возраст человека, выраженный в днях; код, показанный в листинге
20.2, делает это и выполняет неточное преобразование возраста в количество лет.
Листинг 20.2. ntysql_calc_age .php — использование MySQL для вычисления возраста в
годах по дате рождения
<?php
// Определение даты для расчетов
$day = 19;
$month = 9;
$year = 1972;
// Преобразование к формату ISO 8601
$bdayISO = dateC'c", mktime (0, 0, 0, $month, $day, $year)) ;
456
Часть IV. Усовершенствованные технологии РНР
// Использование MySQL-запроса для вычисления возраста в днях
$db = mysqli_connect('localhost', 'user', 'pass');
Sres = mysqli_query ($db, "select datediff(now(), ' $bdayISO') ") ;
$age = mysqli_fetch_array($res) ;
// Преобразование возраста из пней в года (приблизительное)
echo "Возраст: ". floor($age[0]/365.25);
После форматирования дня рождения в метку времени ISO серверу MySQL пере-
дается следующий запрос;
select datediff(now(), '1972-09-18T00:00:00+10:00')
MySQL-функция now () всегда возвращает текущую дату и время. MySQL-функция
datediff (появившаяся в версии MySQL 4.1.1) вычитает одну дату из другой и воз-
вращает разницу в днях.
Очень важно отметить, что вы не выбираете никаких данных из таблиц и даже не
выбираете какую-либо базу данных, однако вы должны подключиться к серверу
MySQL с использованием допустимого имени и пароля.
Использование микросекунд
В некоторых приложениях измерение времени в секундах не обеспечивает доста-
точной точности. Если необходимо измерять более короткие периоды, такие как вре-
мена выполнения PHP-сценариев, следует прибегнуть куслугам функции microtime ().
Если вы пользуетесь РНР5, вызывайте microtime () со значением true параметра
get_as_f loat. Результатом такого вызова будет метка времени в формате числа с пла-
вающей точкой, с которым можно делать все что угодно. Метка времени будет той же,
что возвращается функциями mktime (), time () или date (), однако с дробной частью.
Приведенный ниже оператор
echo number_format(microtime(true), 10, ');
выдает что-то наподобие 1080303003.1321949959.
В более старых версиях получать результат в формате числа с плавающей точкой
было невозможно. Результат всегда представлялся в виде строки. Вызов micro time ()
без параметра возвращает строку вида "0.02149300 1080302326”. Первое число в
этой строке является дробной частью, а второе представляет собой количество пол-
ных секунд, прошедших с момента 1 января 1970 года.
Иметь дело с числами, а не строками, гораздо удобнее, так что если код планиру-
ется выполнять только в РНР5, имеет смысл вызывать функцию microtime () всегда с
параметром true.
Использование календарных функций
В РНР имеется набор функций, позволяющих выполнять преобразования между
различными календарными системами. Наиболее распространенными календарями
являются Григорианский, Юлианский и счетчик Юлианских дней.
Глава 20. Работа с датой и временем
457
Григорианский календарь используется в большинстве западных стран. Дата 15
октября 1582 года по Григорианскому календарю эквивалентна дате 5 октября 1582
года по Юлианскому календарю. До этого момента более распространенным был
Юлианский календарь. Разные страны перешли на Григорианский календарь в раз-
личное время, некоторые — лишь в начале двадцатого века.
Хотя, возможно, вы слышали об этих двух календарях, скорее всего, вы мало что
слышали о счетчике Юлианских дней. Во многом он похож на метки времени Unix.
Это счетчик числа дней, начиная примерно с 4000 года до нашей эры. Сам по себе он
практически бесполезен, но удобен при преобразованиях из одного формата в дру-
гой. Для этого дата сначала преобразуется в значение счетчика Юлианских дней
(Julian Day Count —JD), а затем в требуемый календарный формат.
Для того чтобы воспользоваться этими функциями в Unix, необходимо скомпили-
ровать календарное расширение вместе с РНР, указав опцию - -enable-calendar. Это
расширение по умолчанию входит в состав стандартной установки РНР для Windows.
С целью ознакомления рассмотрим прототипы функций, используемых для пре-
образования из Григорианского календаря в Юлианский:
int gregoriantojd (int month, int day, int year)
string jdtojulian(int julianday)
Для преобразования даты необходимо вызвать обе эти функции:
$jd = gregoriantojd (9, 18, 1582);
echo jdtojulian($jd);
В результате будет выведена юлианская дата в формате ММ/ДД/ГГГГ (месяц/
день/год).
Существует несколько разновидностей этих функций для преобразования даты в
формат Григорианского, Юлианского, Французского и Еврейского календарей, а
также меток времени Unix.
Дополнительные источники информации
Если нужно узнать больше о функциях обработки даты и времени в РНР и MySQL,
можно обратиться к соответствующим разделам справочных руководств:
http://php.net/manual/en/ref.datetime.php
http://www.mysql.com/doc/en/Date_and_time_functions.html
Если требуется преобразовать даты в форматы различных календарей, обратитесь
к руководству по календарным функциям РНР:
http://php.net/manual/en/ref.calendar.php
Что дальше
Одним из уникальных и весьма полезных свойств РНР является создание изобра-
жений на лету. В главе 21 рассматриваются способы применения библиотечных
функций для получения интересных и часто используемых эффектов.
458
Часть IV. Усовершенствованные технологии РНР
21
Генерация изображений
Одна из очень полезных возможностей РНР связана с генерацией изображений
на лету. В РНР для этого предусмотрено несколько встроенных функций, и,
кроме того, для создания новых или изменения существующих изображений можно
воспользоваться библиотекой gd. В этой главе обсуждается, как с помощью функций
обработки изображений можно добиться весьма интересных и полезных эффектов.
В главе, помимо прочих, рассматриваются следующие темы:
Настройка поддержки изображений в РНР.
Форматы изображений.
Создание изображений.
Использование автоматически сгенерированных изображений на других стра-
ницах.
Использование текста и шрифтов для создания изображений.
Рисование фигур и построение графиков.
Мы рассмотрим два конкретных примера: генерация на лету кнопок для Web-сайта
и построение гистограммы по числовым значениям, которые берутся из базы данных
MySQL.
Здесь мы пользуется библиотекой GD2, однако доступна еще одна популярная
библиотека обработки изображений для РНР — ImageMagick. Эта библиотека не вхо-
дит в состав стандартной сборки РНР, но очень легко инсталлируется из библиотеки
классов PHP-расширений (РНР Extension Class Library — PECL). Библиотеки Image-
Magick и GD2 имеют много общего, тем не менее, в ряде областей ImageMagick про-
двинулась гораздо дальше. Если вас интересует создание GIF-изображений (даже с
анимацией), обратитесь к ImageMagick. Если вам нужно работать с изображениями,
представленными в натуральных цветах, или выполнять визуализацию эффектов
прозрачности, вы должны сравнить функциональные возможности, которые предла-
гают обе библиотеки.
ImageMagick для РНР можно загрузить из сайта PECL по адресу:
http://pecl.php.net/package/imagick
Основной сайт ImageMagick, демонстрирующий все возможности и содержащий
полную документацию, находится по адресу http: / /www. imagemagick. org.
Настройка поддержки изображений в РНР
Некоторые функции для работы с изображениями доступны в РНР всегда, однако
большинство из них требует библиотеки GD2. Детальную информацию по GD2 мож-
но найти по адресу http: //www.boutell. сош/gd/.
Начиная с версии 4.3, РНР поставляется со своей версией библиотеки GD2, под-
держиваемой группой разработчиков РНР. Эта версия проще в установке и, как пра-
вило, более устойчива в работе, поэтому, скорее всего, многие отдадут предпочтение
именно ей.
В Windows-версиях автоматически поддерживаются форматы PNG и JPEG.
Если нужно работать с форматом PNG под UNIX, потребуется инсталлировать биб-
лиотеки libpng (http://www.libpng.org/pub/png/) и zlib (http://www.gzip.org/
zlib/).
Кроме того, необходимо сконфигурировать РНР со следующими опциями:
—with-png-dir=/nyib/K/libpng
—with-zlib-dir=/nyTb/K/zlib
Если требуется поддержка JPEG под UNIX, необходимо загрузить модуль jpeg-6b и
перекомпилировать библиотеку GD с включенной поддержкой JPEG-формата. Модуль
доступен для загрузки по адресу ftp://ftp.uu.net/graphics/jpeg/.
Далее следует переконфигурировать РНР с опцией
— with-j peg-dir=/пут ь/к/j peg-6b
и выполнить повторную компиляцию.
Если в изображениях планируется применять TrueType-шрифты, понадобится
также библиотека FreeType, которая входит в состав РНР 4; ее также можно загрузить
из следующего сайта:
http://www.freetype.org/
Для использования вместо TrueType шрифтов PostScript Туре 1 потребуется загру-
зить библиотеку tllib, размещенную на сайте:
ftp://sunsite.unc.edu/pub/Linux/libs/graphics/
После этого следует запустить конфигурационную программу РНР с опцией:
— with-tllib [=путь/к/Г1НЬ]
В конце, естественно, понадобится сконфигурировать РНР с опцией —with-gd.
Форматы изображений
Библиотека GD поддерживает форматы JPEG, PNG и WBMP. Формат GIF больше
не поддерживается. Давайте кратко рассмотрим каждый из этих форматов.
JPEG
JPEG обозначает Joint Photographic Experts Group (Объединенная группа экспертов по
фотографии) и является названием группы стандартов, а не каким-то специфическим
форматом. Формат файла, который обычно называют JPEG, в действительности име-
ет hmhJFIF и соответствует одному из стандартов, выпущенных группой JPEG.
460
Часть IV. Усовершенствованные технологии РНР
На всякий случай напомним, что формат JPEG обычно используется для хранения
фотографических или других изображений с большим количеством цветов или от-
тенков. В этом формате используется сжатие с потерями, то есть при уменьшении
размера файла теряется качество. Поскольку формат JPEG предназначен преимуще-
ственно для хранения аналоговых изображений с оттенками цветов, то человеческий
глаз может не заметить потери качества. Этот формат не подходит для хранения чер-
тежей, текста или больших одноцветных областей.
Прочесть oJPEG/JFIF можно на официальном сайте JPEG, который находится по
адресу http://www.jpeg.org/.
PNG
PNG обозначает Portable Network Graphics (Переносимая сетевая графика). Этот
формат файла рассматривается как замена формата GIF (Graphics Interchange For-
mat — Формат обмена графическими изображениями) по причинам, о которых будет
рассказано ниже. На Web-сайте PNG этот формат описан как “формат изображений
со сжатием без потерь”. Поскольку потерь не происходит, этот формат подходит для
изображений, содержащих текст, прямые линии и блоки одного цвета, как, напри-
мер, заголовки или кнопки на Web-сайте — все те элементы, для представления кото-
рых ранее использовались GIF-изображения.
Сжатая PNG-версия изображения характеризуется сравнимыми размерами сжатой
GIF-версии. В PNG также применяется переменная прозрачность, гамма-коррекция и
двойное чередование. Однако он не поддерживает анимацию — для этого можно вос-
пользоваться расширенным форматом MNG, который еще находится в стадии разра-
ботки.
Схемы сжатия без потерь хороши для хранения иллюстраций, но не особенно
подходят для хранения больших фотографий, поскольку в этом случае получаются
файлы громадных размеров.
Прочесть о формате PNG можно на официальном сайте PNG, находящемся по ад-
ресу http://www.1ibpng.org/pub/png/.
WBMP
WBMP обозначает Wireless Bitmap (Битовое изображение для беспроводной связи).
Этот формат был специально разработан для устройств беспроводной связи. В на-
стоящее время он используется не особенно широко.
GIF
GIF обозначает Graphics Interchange Format (Формат обмена графическими изобра-
жениями). Это формат со сжатием без потерь, широко используемый в Internet для
хранения изображений, содержащих текст, линии и блоки одного цвета.
Возникает естественный вопрос: почему GD не поддерживает формат GIF?
Ответ заключается в том, что поддержка существовала вплоть до версии 1.3. Для
некоторых старых версий РНР можно загрузить и установить библиотеку GD версии
1.3, которая доступна по следующему адресу:
http://www.linuxguruz.org/downloads/gdl.3.tar.gz
Глава 21. Генерация изображений
461
Обратите, однако, внимание, что разработчики GD не рекомендуют использовать
эту версию и больше не поддерживают ее. Кроме того, GD 1.3 побуждает использо-
вать устаревшие версии РНР. Стоит также учесть, что эта версия с поддержкой GIF
вскорости может оказаться вообще недоступной.
Существует немаловажная причина, почему библиотека GD больше не поддержи-
вает запись файлов в GIF-формате (хотя позволяет их читать). В стандарте GIF при-
меняется форма сжатия LZW (Lempel Ziv Welch — Лемпеля, Зива, Вельха), которая
запатентована компанией UNISYS. После того, как после многих лет эксплуатации
формат GIF стал стандартом де-факто, UNISYS решила, что разработчики программ,
читающих и записывающих файлы в формате GIF, должны платить UNISYS лицензи-
онные отчисления. Платит отчисления, например, компания Adobe, чьи продукты,
подобные Photoshop, используются для создания GIF-файлов. Библиотеки кодов по-
пали в ситуацию, когда должны платить отчисления и авторы этих библиотек, и их
пользователи. Таким образом, если вы на своем Web-сайте применяете версию биб-
лиотеки GD с поддержкой GIF, то это значит, что вы должны платить UNISYS весьма
ощутимые лицензионные отчисления. Функции чтения GIF не подпадают под дейст-
вие этой лицензии, поэтому они продолжают быть доступными.
Это довольно-таки плачевная ситуация, поскольку формат GIF применялся на
протяжении многих лет, пока компания UNISYS не инициировала его лицензирова-
ние. Фактически этот формат стал одним из стандартов Internet. Сообщество разра-
ботчиков Internet очень негативно отнеслось к этому патентованию. Вы можете по-
читать об этом (и сформировать собственное мнение) на сайте UNISYS по адресу
http://www.Unisys.com/about_Unisys/Izw/.
Можете также ознакомиться с позицией противников — Burn All Gifs (“Сожгите
все GIF-файлы”) — по адресу http: //burnallgifs.org/.
Срок действия патента LZW в США истек 21 июня 2003 года, а в Европе и
Азии — в различное время на протяжении 2004 года. Мы, авторы этой книги, не яв-
ляемся адвокатами, поэтому ничего из сказанного нельзя воспринимать как совет
профессионального юриста, однако, по нашему мнению, проще использовать PNG и
не зависеть от лицензионной политики GIF. Поддержка PNG в браузерах ранее была
не особенно хорошей, тем не менее, в браузерах последних версий она стала удачной.
Усовершенствованные возможности PNG пока поддерживаются фрагментарно, од-
нако простые PNG-изображения должны работать везде.
Ожидается (во всяком случае, по доступной на данный момент информации),
что поддержка записи GIF-изображений будет восстановлена в версиях РНР 5.0.1
и РНР 4.3.8, выпуск которых запланирован сразу же после истечения срока дейст-
вия патента LZW.
Создание изображений
Ниже представлены четыре основных шага по созданию изображений в РНР:
1. Создание холста, предназначенного для дальнейшей работы.
2. Вычерчивание форм или вывод текста на этом холсте.
3. Вывод финального изображения.
4. Освобождение ресурсов.
462
Часть IV. Усовершенствованные технологии РНР
Начнем с рассмотрения очень простого сценария создания изображения, кото-
рый показан в листинге 21.1.
Листинг 21.1. simplegraph. php—вывод простого линейного графика с текстом “Sales” (Продажи)
// Настройка изображения
$height = 200;
$width = 200;
$im = ImageCreateTrueColor($width, $height);
$white = ImageColorAllocate ($im, 255, 255, 255);
$blue = ImageColorAllocate ($im, 0, 0, 64);
// Отрисовка изображения
ImageFill($im, 0, 0, $blue);
ImageLine($im, 0, 0, $width, $height, $white);
Imagestring($im, 4, 50, 150, 'Продажи', $white);
// вывод изображения
Header ('Content-type: image/png');
ImagePng ($im);
// освобождение ресурсов
ImageDestroy($im);
Результат работы сценария можно видеть на рис. 21.1.
Рис. 21.1. Сценарий вначале создает синий фон, а затем добавляет
линию и надпись
Рассмотрим все шаги создания изображения более подробно.
Создание холста
Для того чтобы приступить к созданию или изменению изображения в РНР, необ-
ходимо создать идентификатор изображения. Это можно сделать двумя основными
Глава 21. Генерация изображений
463
способами. Первый заключается в создании пустого холста с помощью вызова функ-
ции ImageCreateTrueColor (), что и предпринято в сценарии:
$im = ImageCreateTrueColor($width, $height);
Функция ImageCreateTrueColor () требует передачи двух параметров. Первый из
них задает ширину нового изображения, а второй — высоту. Функция возвращает
идентификатор нового изображения. (Эти идентификаторы очень похожи на деск-
рипторы файлов.)
Другой способ связан с чтением файла существующего изображения, после чего к
нему можно применить фильтры, изменить размер или добавить что-либо. В зависимо-
сти от формата введенного файла, это можно сделать с помощью одной из функций
ImageCreateFromPNG(), ImageC'reateFromJPEG() или ImageCreateFromGIF(). Пара-
метром каждой из функций является имя файла, например:
$im = ImageCreateFromPNG('baseimage.png');
Далее в этой главе приведен пример, в котором для создания кнопок на лету ис-
пользуется существующее изображение.
Рисование и вывод текста в изображение
Рисование или вывод текста в изображение выполняется в два этапа. Сначала не-
обходимо выбрать цвета, которые будут использоваться при рисовании. Возможно,
вы уже знаете, что цвета на мониторе компьютера формируются за счет смешения
красного, зеленого и синего компонент. Форматы изображения используют цветовую
палитру, которая состоит из заданного подмножества всех возможных комбинаций
трех цветов. Чтобы использовать цвет для рисования, его необходимо добавить к
палитре изображения. И это нужно сделать для каждого цвета, даже для черного и
белого.
Цвета для изображения можно выбрать, вызвав функцию ImageColorAl locate (). Ей
необходимо передать идентификатор изображения и значения красной, зеленой и
синей (RGB) компонент требуемого цвета.
В листинге 21.1 используются два цвета: синий и белый. Они определяются при
помощи вызовов функций:
$white = ImageColorAllocate ($im, 255, 255, 255);
$blue = ImageColorAllocate ($im, 0, 0, 64);
В результате возвращается идентификатор цвета, который можно использовать
для последующего доступа к данному цвету.
Далее, чтобы формировать собственно изображение, существует несколько различ-
ных функций, выводящих нужные объекты: линии, дуги, многоугольники или текст.
Функции рисования обычно используют следующие параметры:
Идентификатор изображения.
Начальные и, при необходимости, конечные координаты изображаемого объ-
екта.
Цвет объекта.
Информация о шрифте для вывода текста.
464
Часть IV. Усовершенствованные технологии РНР
В сценарии использованы три таких функции. Рассмотрим каждую из них по от-
дельности.
Вначале с помощью функции ImageFill () был создан черный фон:
ImageFill($im, 0, 0, $blue);
Параметрами этой функции являются идентификатор изображения, начальные
координаты заполняемой области (х и у) и цвет заливки.
Примечание
Обратите внимание, что координаты точки в изображении отсчитываются от левого верхнего
угла, для которого х=0, у=0. Координаты правого нижнего угла изображения x=$width,
y=$height. Это не совпадает со стандартными соглашениями вывода графики, поэтому будь-
те аккуратны!
Затем из левого верхнего (0, 0) в правый нижний ($width, $height) угол изобра-
жения проводится прямая линия:
ImageLine($im, 0, 0, $width, $height, $white);
Параметрами этой функции являются идентификатор изображения, начальные и
конечные точки линии и цвет.
В заключение на изображение помещается метка:
Imagestring($im, 4, 50, 150, 'Sales', $white);
Функции Imagestring () требуются несколько иные параметры. Ее прототип име-
ет вид:
int Imagestring (resource im, int font, int x, int y, string s, int col)
Параметрами являются: идентификатор изображения, шрифт, координаты х и у
начальной точки текста, выводимый текст и его цвет.
Шрифт задается числом от 1 до 5. Этот диапазон отвечает набору встроенных
шрифтов. Альтернативой этому является применение шрифтов TrueType или Post-
Script Туре 1. Каждому из этих наборов шрифтов соответствует свой набор функций.
В следующем примере будет продемонстрировано применение функций для шрифтов
TrueType.
Гораздо лучше использовать набор функций одного из альтернативных шрифтов,
так как текст, выводимый функцией Imagestring () и ей подобными, например,
ImageChar() (вывод символа в изображение), получается ступенчатым. Функции
шрифтов TrueType и PostScript воспроизводят текст со сглаживанием.
Если вы не в курсе, в чем разница, взгляните на рис. 21.2. Там, где шрифт содер-
жит кривые или наклонные линии, текст имеет зазубренные края. Так происходит
потому, что кривая или наклонная линия достигается при помощи “эффекта лестни-
цы”. В сглаженном изображении кривые или наклонные линии содержат точки, про-
межуточные между цветом текста и фона. В результате текст выглядит гораздо акку-
ратнее.
Глава 21. Генерация изображений
465
Normal
Anti-aliased
Рис. 21.2. Обычный текст (надпись “Normal”) имеет
зазубренные края, особенно при большом размере
шрифта. Сглаживание выравнивает кривые и углы
букв (см. надпись “Anti-aliased”)
Вывод финального изображения
Изображение можно вывести либо непосредственно в браузер, либо в файл.
В нашем примере вывод изображения выполняется в браузер. Этот процесс со-
стоит из двух шагов. Вначале необходимо сообщить Web-браузеру, что будет выво-
диться именно изображение, а не текст или HTML-код. Это достигается с помощью
функции Header (), которая указывает MIME-тип изображения:
Header ('Content-type: image/png');
Обычно при получении файла браузером первое, что отправляет Web-сервер —
это MIME-тип. Для HTML- или PHP-страницы (после выполнения кода), заголовок
имеет вид:
Content-type: text/html
Он сообщает браузеру, как необходимо интерпретировать последующие данные.
В нашем случае требуется сообщить браузеру, что пересылается изображение, а не
обычный HTML-вывод. Это можно сделать с помощью функции Header (), которая
пока еще не обсуждалась.
Данная функция пересылает строки HTTP-заголовков. Другим типичным их при-
менением является HTTP-перенаправление. Оно заставляет браузер загружать вместо
запрашиваемой другую страницу. Обычно так делается в случае перемещения стра-
ницы. Например:
Header ('Location: http://www.example.com/new_home_page.html');
Важно отметить, что функция Header () не может быть выполнена, если НТТР-
заголовок страницы уже был отправлен. РНР посылает HTTP-заголовки автоматиче-
ски всякий раз, когда происходит вывод чего-либо в браузер. Следовательно, наличие
любого оператора echo или даже просто пробельного символа перед открывающим
PHP-дескриптором приводит к отправке заголовка, вследствие чего при попытке вы-
зова функции Header () РНР выдаст предупреждающее сообщение. Тем не менее,
можно переслать несколько HTTP-заголовков с помощью нескольких вызовов функ-
ции Header () в одном и том же сценарии, хотя все они должны появиться до первого
вывода информации в браузер.
466
Часть IV. Усовершенствованные технологии РНР
После отправки заголовка можно вывести изображение посредством вызова сле-
дующей функции:
ImagePng ($im);
В результате в браузер будет выведено изображение в формате PNG. Если требу-
ется другой формат, можно воспользоваться функцией TmageJPEG (), если включена
поддержка JPEG. Разумеется, вначале необходимо отправить соответствующий заго-
ловок, то есть:
Header ('Content-type: image/jpeg');
Вторая возможность, в отличие от всех предыдущих, заключается в выводе
изображения в файл, а не браузер. Этого можно добиться, добавив в функцию
ImagePNG () (или аналогичную функцию для другого формата) необязательный второй
параметр:
ImagePNG($ im, $ f i1ename);
He забывайте, что при этом действуют все правила записи в файл из РНР (напри-
мер, необходимость корректной настройки прав доступа).
Освобождение ресурсов
По окончании работы с изображением необходимо освободить используемые ре-
сурсы, уничтожив идентификатор изображения. Это делается с помощью функции
ImageDestroy():
ImageDestroy($im);
Использование автоматически
сгенерированных изображений
на других страницах
Поскольку заголовок может быть переслан лишь один раз, и это единственный
способ сообщить браузеру, что передается изображение, вставлять изображения, ге-
нерируемые на лету, в обычные страницы не особенно просто. Для этого существуют
три перечисленных ниже способа:
1. Вся страница может состоять из рисунка, как это было в предыдущем примере.
2. Можно, как упоминалось ранее, сохранить изображение в файле, а затем
ссылаться на него с использованием обычного HTML-дескриптора <IMG>.
3. В дескриптор изображения можно поместить сценарий, создающий изобра-
жение.
О методах 1 и 2 уже было рассказано. Рассмотрим вкратце метод 3. Для этого изо-
бражение вставляется в HTML-код с помощью следующего HTML-дескриптора изо-
бражения:
<img src="simplegraph.php" height="200“ width=”200"
alt="Объем продаж падает" />
Глава 21. Генерация изображений
467
Вместо непосредственной вставки PNG-, JPEG- или GIF-изображения в параметре
SRC дескриптора указан сценарий, генерирующий финальное изображение. Это по-
зволяет получить и вывести встроенное изображение, как показано на рис. 21.3.
Рис. 21.3. Динамически сгенерированный рисунок выглядит для
конечного пользователя так же, как и созданный традиционным
способом, в графическом редакторе
Использование текста и шрифтов при
создании изображений
Теперь рассмотрим более сложный пример. Иногда удобно автоматически созда-
вать кнопки или другие изображения для Web-сайта. Простые кнопки в виде прямо-
угольника фонового цвета можно легко создать ранее рассмотренными методами.
Можно также сгенерировать более сложные эффекты программно, однако в общем
случае их проще получить с помощью какого-нибудь графического редактора. Кроме
того, разумнее возложить создание изображений на художников, а код — на програм-
мистов.
В данном примере будут сгенерированы кнопки, использующие пустой шаблон,
который позволяет создавать эффекты наподобие скошенных краев и тому подобно-
го, обеспечиваемые Photoshop, GIMP и другими графическими редакторами. Ис-
пользуя графическую библиотеку РНР, можно взять базовое изображение и затем
рисовать поверх него.
Кроме того, мы используем TrueType-шрифты, чтобы получить сглаженный текст.
У функций работы с TrueType-шрифтами есть свои особенности, о которых будет
рассказано далее.
Задача, в основном, состоит в том, чтобы взять некоторый текст и создать кнопку
с этим текстом на ней. Текст потребуется центрировать на кнопке по горизонтали и
вертикали и подобрать максимально возможный размер шрифта, помещающийся в
кнопку.
Для тестирования и экспериментирования разработан специальный интерфейс.
Этот интерфейс показан на рис. 21.4. (HTML-код для этой формы здесь не показан,
468
Часть IV. Усовершенствованные технологии РНР
поскольку он очень прост; желающие могут найти его на прилагаемом к книге ком-
пакт-диске в файле chapter21 \design_button. html.)
Рис. 21.4. Данный интерфейс позволяет выбирать цвет кнопки и
вводить текст
Подобный интерфейс можно задействовать в программах автоматической гене-
рации Web-сайтов. Сценарий, представленный здесь, можно встроить в HTML-код и
генерировать на лет}' все кнопки Web-сайта, однако это может быть сопряжено с
чрезмерными затратами времени.
Типичный пример вывода сценария показан на рис. 21.5.
Кнопка генерируется с помощью сценария make_button.php, текст которого
представлен в листинге 21.2.
Листинг 21.2. make button.php — этот сценарий можно вызвать из формы в
designbutton.html или же из HTML-дескриптора изображения
<?php
$button_text = $_REQUEST['button_text'];
$color = $—REQUEST['color'];
// Проверка, что в переменных button_text и color
// содержатся требуемые данные
if (empty($button_text) || empty($color) || (!($color=='red'
|| $color=='blue' || $color=='green')))
(
echo 'Форма заполнена некорректно. Создание изображения невозможно.';
exit;
}
// Создание изображения с требуемым фоном и проверка его размера
$im = ImageCreateFromPNG ($color.'-button.png');
$width_image = ImageSX($im);
$height_image = ImageSY($im);
Глава 21. Генерация изображений
469
// Нашим изображениям требуется отступ 18 пикселей от края
$width_image_wo_margins = $width_image - (2 * 18) ;
$height_image_wo_margins = $height_image - (2 * 18);
// Проверка, подходит ли размер шрифта, и уменьшение его, пока он не подойдет.
// Начинаем с наибольшего размера, который может подойти для кнопок
$font_size = 33;
// Необходимо указать gd2, где находятся шрифты
putenv('GDFONTPATH=C:\WINDOWS\Fonts1);
$fontname = 'arial';
do
{
$font_size-~;
// Находим размер текста при данном размере шрифта
$bbox=ImageTTFBBox ($font_size, 0, $fontname, $button_text);
$right_text = $bbox[2]; // правая координата
$left_text = $bbox[0]; // левая координата
$width_text = $right_text - $left_text; 11 ширина надписи
$height_text = abs($bbox[7] - $bbox[l]); // высота надписи
}
while ( $font_size>8 &&
( $height_text>$height_image_wo_margins ||
$width_text>$width_image_wo_margins )
);
if ( $height_text>$height_image_wo_margins ||
$width_text>$width_image_wo_margins )
{
// Невозможно подобрать шрифт для текста
echo 'Текст не помещается на кнопку.<br />';
}
else
(
// Найден подходящий размер шрифта.
// Поиск места для размещения текста
$text_x = $width_image/2.О - $width_text/2.0;
$text_y = $height_image/2.0 - $height_text/2.0 ;
if ($left_text < 0)
$text_x += abs($left_text); // увеличение отступа слева
$above_line_text = abs($bbox[7]); // как далеко от базовой линии?
$text_y += $above_line_text; // увеличение отступа сверху
$text_y -= 2; // корректировка отступа для данного шаблона
$white = ImageColorAllocate ($im, 255, 255, 255);
ImageTTFText ($im, $font_size, 0, $text_x, $text_y,
$white, $fontname, $button_text);
Header ('Content-type: image/png');
ImagePNG ($im);
}
ImageDestroy ($im);
470
Часть IV. Усовершенствованные технологии PHP
Это один из самых длинных сценариев из числа приведенных до сих пор. Рас-
смотрим его последовательно, по частям. Он начинается с набора стандартных про-
верок на ошибки, а затем производится настройка холста.
Рис. 21.5. Кнопка, сгенерированная сценариемmake_button.php
Настройка базового холста
В листинге 21.2 кнопка не создается с нуля, а используется уже существующее изо-
бражение. Имеется возможность выбрать кнопку одного из трех цветов: красный
(red-button.png), зеленый (green-button.png) и синий (blue-button.png).
Цвет, выбранный пользователем в форме, сохраняется в переменной $ color.
Все начинается с извлечения цвета из суперглобального массива $_REQUEST () и
установки нового идентификатора изображения на основе выбранной пользователем
кнопки:
$color = $-REQUEST[1 color'] ;
$im = ImageCreateFromPNG ($color.'-button.png');
Функция ImageCreateFromPNG () принимает в качестве параметра имя PNG-файла
и возвращает новый идентификатор для изображения, содержащего копию этого
файла. Обратите внимание, что исходный PNG-файл при этом не меняется. При
включенной поддержке соответствующих форматов аналогично можно использовать
функции ImageCreateFromJPEG () и ImageCreateFromGIF ().
Примечание
Функция ImageCreateFromPNG() создает изображение только в памяти. Чтобы сохранить
изображение в файл или вывести его в браузер, нужно вызвать функцию imagePNG (), кото-
рая будет рассматриваться далее в этой главе.
Глааа 21. Генерация изображений
471
Подбор размера текста на кнопке
В переменной $button_text хранится текст, введенный пользователем. Этот
текст необходимо вывести на кнопке шрифтом максимального размера. Задача реша-
ется с помощью итераций или, строго говоря, итеративным методом проб и ошибок.
Сначала устанавливаются все требуемые переменные. В первых двух хранятся вы-
сота и ширина кнопки:
$width_image = ImageSX($im);
$height_image = ImageSY($im);
Две следующих переменных хранят отступы от краев кнопки. Края кнопки скруг-
лены, поэтому необходимо оставить пустым некоторое пространство возле края. Для
других исходных изображений эти параметры будут другими! В данном случае отступ
от каждого края составляет 18 пикселей.
$width_image_wo_margins = $width_image - (2 * 18);
$height_image_wo_margins = $height_image - (2 * 18);
Далее нужно установить начальный размер шрифта. Его значение равно 32 (вооб-
ще-то 33, но он тут же уменьшается на единицу), поскольку это шрифт наибольшего
размера, который может вообще поместиться на кнопке:
$font_size = 33;
Если используется библиотека GD2, нужно указать, где размещены используемые
шрифты; для этого устанавливается системная переменная GDFONTPATH:
putenv('GDFONTPATH=C:\WINDOWS\Fonts’);
Еще необходимо задать название нужного шрифта. Он будет использоваться
функциями работы с TrueType-шрифтами, которые в вышеуказанном каталоге будут
искать файл описания шрифта с именем, эквивалентным названию шрифта, и рас-
ширением . ttf:
Sfontname = 'arial';
Возможно, ваша операционная система потребует, чтобы название шрифта окан-
чивалось на “. ttf’’.
Если в вашей системе нет шрифта Arial (который используется в данном приме-
ре), его можно заменить любым другим TrueType-шрифтом.
Затем выполняется цикл уменьшения размера шрифта до тех пор, пока заданный
текст не поместится на кнопке:
do
{
$font_size—;
// Находим размер текста при данном размере шрифта
$bbox=ImageTTFBBox ($font_size, 0, $fontname, $button_text);
$right_text = $bbox[2]; // правая координата
$left_text = $bbox[0]; // левая координата
$width_text = $right_text - $left_text; // ширина надписи
$height_text = abs($bbox[7] - $bbox[l]); // высота надписи
}
472
Часть IV. Усовершенствованные технологии РНР
while ( $font_size>8 &&
( $height_text>$height_image_wo_margins ||
$width_text>$width_image_wo_margins )
Этот фрагмент кода проверяет размер шрифта, используя ограничивающий прямо-
угольник (bounding box) текста. Его размеры возвращает функция ImageGetTTFBBox(),
входящая в состав набора функций работы с TrueType-шрифтами. После определе-
ния размера шрифта надпись выводится TrueType-шрифтом (в данном случае Arial,
но его можно заменить на любой другой) с помощью функции ImageTTFText ().
Ограничивающий прямоугольник текста — это наименьший прямоугольник, в ко-
торый можно вписать текст. Пример такого прямоугольника показан на рис. 21.6.
.Our Company
Рис. 21.6. Координаты ограничивающего прямо-
угольника задаются относительно базовой линии.
Начало координат обозначено как (0,0)
Для определения размеров прямоугольника используется следующий вызов:
$bbox=ImageTTFBBox ($font_size, 0, $fontname, $button_text);
Это означает: “Каковы размеры текста $button_text, набранного TrueType-
шрифтом Arial с наклоном ноль градусов и размером $font_size?”.
Следует отметить, что функции необходимо передать путь к файл}’, содержащему
шрифт. В данном случае он находится в том же каталоге, что и сценарий (каталоге по
умолчанию), поэтому путь здесь не указан.
Функция возвращает массив, содержащий координаты углов ограничивающего
прямоугольника. Содержимое этого массива показано в табл. 21.1.
Таблица 21.1. Содержимое массива ограничивающего прямоугольника
Индекс массива Содержимое
0 Координата х левого нижнего угла.
1 Координата у левого нижнего угла.
2 Координата х правого нижнего угла.
3 Координата у правого нижнего угла.
4 Координата х правого верхнего утла.
5 Координата у правого верхнего угла.
6 Координата х левого верхнего угла.
7 Координата у левого верхнего угла.
Чтобы запомнить, как расположены координаты в массиве, достаточно помнить,
что нумерация начинается с левого нижнего угла и продолжается против часовой
стрелки.
Глава 21. Генерация изображений
473
Значения, возвращаемые функцией ImageTTFBBox (), имеют одно “скользкое” ме-
сто. Они являются координатами, отсчитываемыми от некоторого начала. Однако, в
отличие от координат изображения, центром для которых служит левый верхний
угол, они отсчитываются от базовой линии.
Вернемся вновь к рис. 21.6. Линия, соприкасающаяся с большинством букв снизу,
называется базовой линией (baseline). Части некоторых букв расположены под ней, как
у буквы у. Они называются нижними выносными элементами (descenders).
Левая точка базовой линии считается началом координат, то есть она имеет коор-
динаты х и у, равные 0. Точки над базовой линией имеют положительную координату
х, а под ней — отрицательную.
Кроме того, координаты текста могут находиться за пределами ограничивающего
прямоугольника. Например, координата х начала текста может быть равна -1.
Все это значит, что при выполнении операций над координатами требуется про-
являть определенную осторожность.
Определение ширины и высоты текста выполняется следующим образом:
$right_text = $bbox[2]; // правая координата
$left_text = $bbox[0]; // левая координата
$width_text = $right_text - $left_text; // ширина надписи
$height_text = abs($bbox[7] - $bbox[l]J; // высота надписи
После этого проверяется условие цикла:
} while ( $font_size>8 &&
( $height_text>$height_image_wo_margins ||
$width_text>$width_image_wo_margins )
) ;
Здесь проверяется сразу два набора условий. Первый — что шрифт все еще являет-
ся читабельным, поскольку не имеет смысла выводить надпись шрифтом, размер ко-
торого меньше 8 пунктов. Второй набор проверяет, помещается ли текст внутри от-
веденной для него области.
Затем проверяется, удалось ли с помощью итеративной процедуры подобрать
подходящий размер шрифта, и если нет, выводится сообщение об ошибке:
if ( $height_text>$height_image_wo_margins ||
$wi dth_text> $width_image_wo_margins )
{
// Невозможно подобрать шрифт для текста
echo 'Текст не помещается на кнопку.<br />';
}
Позиционирование текста
Если проблем не возникло, далее нужно вычислить позицию начала текста. Это
центр прямоугольника, остаюшегося после размещения текста:
$text_x = $width_image/2.0 - $width_text/2.0;
$text_y = $height_image/2.0 - $height_text/2.0 ;
Из-за сложностей, связанных с координатной системой базовой линии, необхо-
димо выполнить коррекцию координат:
474
Часть IV. Усовершенствованные технологии РНР
if ($left_text < 0)
$text_x += abs($left_text); // увеличение отступа слева
$above_line_text = abs($bbox[7]); // как далеко от базовой линии?
$text_y += $above_line_text; // увеличение отступа сверху
$text_y -= 2; // корректировка отступа для данного шаблона
Это немного выравнивает изображение, слегка “перегруженное вверху”.
Вывод текста на кнопку
Теперь остается лишь вывести текст. Для него выбран белый цвет:
$white = ImageColorAllocate ($im, 255, 255, 255);
Сам текст выводится с помощью функции ImageTTFText ():
ImageTTFText ($im, $font_size, 0, $text_x, $text_y, $white, $fontname,
$button_text);
Этой функции требуется множество параметров (по порядку): идентификатор
изображения, размер шрифта в пунктах, угол наклона текста, координаты х и у на-
чальной точки, цвет текста, файл шрифта и, наконец, собственно текст, который бу-
дет выведен на кнопке.
Примечание
Файл шрифта должен быть доступен на сервере, а на клиентской машине его присутствия не
требуется, поскольку к пользователю текст поступает в виде изображения.
Заключительные действия
Теперь изображение с кнопкой выводится в браузер:
Header ('Content-type: image/png');
ImagePng ($im) ;
После этого остается только освободить ресурсы:
ImageDestroy ($im);
Вот и все! При успешном исходе в окне браузера должна появиться кнопка, похо-
жая на показанную на рис. 21.5.
Вычерчивание фигур и построение
графиков
В последнем примере приложения обсуждалась обработка существующих изобра-
жений и текста. Пока еще ничего не было сказано о вычерчивании геометрических
фигур, и этот пробел сейчас будет восполнен.
В нижеследующем примере создается система голосования, размещенная на Web-
сайте — для предварительного опроса, за кого будут голосовать пользователи на (спо-
койно, фиктивных) выборах. Результаты будут храниться в базе данных MySQL, и по
ним с помощью графических функций будет строиться гистограмма.
Глава 21. Генерация изображений
475
Одно из основных применений функций работы с изображениями — вычерчива-
ние графиков. Данными могут быть объемы продаж, количество посещений Web-
сайта, да и вообще все, что угодно.
Для этого примера создана база данных MySQL с именем poll (“выборы”). Она со-
держит одну таблицу poll_results, состоящую из двух столбцов: candidate — имена
кандидатов и num_votes — количество отданных за них голосов. Для доступа к базе дан-
ных создан пользователь с именем poll и паролем poll. Для настройки требуется не-
сколько минут — достаточно запустить SQL-сценарий, показанный в листинге 21.3. Мож-
но просто перенаправить этот сценарий, зарегистрировавшись как пользователь root:
mysql -u root -p < pollsetup.sql
Естественно, можно воспользоваться учетной записью любого пользователя, об-
ладающего необходимыми правами доступа к MySQL.
Листинг 21.3. pollsetup.sql —установка базы данных poll
create database poll;
use poll;
create table poll_results (
candidate varchar(30),
num_votes int
) ;
insert into poll_results values
(1 John Smith' , 0) ,
('Mary Jones', 0) ,
('Fred Bloggs', 0)
grant all privileges
on poll.*
to poll@localhost
identified by 'poll';
Эта база содержит информацию о трех кандидатах. Интерфейс для голосования
обеспечивается страницей vote.html, код которой приведен в листинге 21.4.
Листинг 21.4. vote.html — здесь посетители могут проголосовать
<html>
<head>
<title>ronocoBaHne</title>
<head>
<body>
<Ь1>Голосуем, голосуем, голосуем...</hl>
<р>3а кого вы проголосуете на предстоящих выборах?</р>
<form method="post" action="show_poll,php">
cinput type="radio" name=''vote" value="John Smith">
John Smithcbr />
cinput type="radio" name="vote" value="Mary Jones">
Mary Jonescbr />
<input type="radio" name=”vote" value="Fred Bloggs”>
Fred Bloggscbr /xbr />
cinput type="submit" value=”Результаты”>
</form>
476
Часть IV. Усовершенствованные технологии PHP
</body>
Вид страницы в браузере показан на рис. 21.7.
Рис. 21.7. На этой странице пользователи могут проголосовать, а
щелчок на кнопке Результаты приведет к выводу на экран текуще-
го состояния результатов голосования
Общая идея такова: когда пользователь щелкает на кнопке Результаты, его голос
добавляется в базу данных, затем оттуда читаются все голоса и выводится гистограм-
ма текущего состояния результатов.
Типичный пример вывода после нескольких сот голосований показан на рис. 21.8.
Сценарий, создающий это изображение, достаточно длинен. Поэтому он разбит
на четыре части, которые обсуждаются по отдельности.
Рис. 21.8. Результаты голосования отображаются за счет рисо-
вания на холсте последовательности линий, прямоугольников и
текстовых элементов
Глава 21 Генерация изображений
477
Большая часть сценария выглядит знакомой, поскольку ранее приводилось доста-
точно много похожих примеров работы с MySQL, а также одноцветной заливки фо-
нового холста и вывода на нем текстовых меток.
Новыми в сценарии являются фрагменты, отвечающие за рисование линий и
прямоугольников. Внимание прежде всего будет сосредоточено на них. Часть 1 (это-
го сценария из четырех частей) представлена в листинге 21.5.1.
Листинг 21.5.1. showpoll.php —часть 1 сценария обновляет базу данных и извлекает
из нее новые результаты
<?php
/*************************************************************
Запрос к базе данных для получения информации о голосовании
*************************************************************/
// Получение значения из формы
$vote=$_REQUEST['vote'];
// Регистрация в базе данных
if (!$db_conn = new mysqli('localhost', 'poll', 'poll', 'poll'))
{
echo 'Ошибка доступа к базе данных<Ьг />';
exit;
};
if (!empty($vote)) // если форма заполнена, добавить голос
{
$vote = addslashes($vote);
Squery = "update poll_results
set num_votes = num_votes + 1
where candidate = '$vote'”;
if(!(gresult = $db_conn->query(Squery)))
(
echo 'Ошибка доступа к базе данных<Ьг />';
exit;
}
};
// Запрос текущих результатов голосования независимо
// от того, проголосовал ли данный пользователь
Squery = 'select * from poll_results';
if(!(Sresult = $db_conn->query(Squery)))
{
echo 'Ошибка доступа к базе данных<Ьг />';
exit;
}
$num_candidates = $result->num_rows;
// Подсчет общего количества голосов
$total_votes=0;
while ($row = $result->fetch_object())
{
$total_votes += $row->num_votes;
J
$result->data seek(O); // сброс указателя Sresult
478
Часть IV. Усовершенствованные технологии РНР
Код в части I, показанной в листинге 21.5.1, подключается к базе данных MySQL,
обновляет данные на основе пользовательского ввода и запрашивает обновленные
данные. Располагая этой информацией, можно произвести расчеты, необходимые
для построения графика. Вторая часть сценария показана в листинге 21.5.2.
Листинг 21.5.2. showpoll .php — в части 2 сценария вычисляются все переменные,
необходимые для рисования
Предварительные расчеты для построения графика
*************************************************/
// Установка необходимых констант
putenv('GDFONTPATH=C:\WINDOWS\FontS');
$width=500; // ширина изображения в пикселях - уместится в экран 640x480
$left_margin =50; // отступ слева от графика
$right_margin = 50; // отступ справа от графика
$bar_height = 40;
$bar_spacing = $bar_height/2;
$font = 'arial'; $title_size = 16; И в пунктах
$main_size = 12; И в пунктах
$small_size = 12; и в пунктах
$text_indent = 10; и позиция текстовых меток от края изображения
// Установка начальной точки для рисования
$х = $left_margin + 60; II место базовой линии рисунка
$у = 50; // то же
$bar_unit = ($width-($x+$right_margin)) / 100; // одна “точка" на гистограмме
// Подсчет высоты прямоугольников плюс промежуток плюс поле
$height = $num candidates * ($bar height + $bar spacing) + 50;
В части 2 рассчитываются переменные, необходимые для вычерчивания графика.
Вычисление значений этих переменных несколько утомительно, однако предва-
рительная прикидка, как должно выглядеть окончательное изображение, существен-
но упростит процесс рисования. Используемые здесь значения были получены с по-
мощью эскиза на бумаге и приближенной оценки требуемых пропорций.
Переменная $width содержит общую ширину изображения. $left_margin и
$right_margin — отступы слева и справа соответственно, $bar_height и $bar_spacing —
толщину полос и расстояние между ними, $font, $title_size, $main_size,
$small_size и $text_indent — шрифт, его размеры и положение меток.
По этим базовым значениям можно рассчитать остальные величины. Вначале не-
обходимо нарисовать базовую линию, от которой будут начинаться все полосы гисто-
граммы: отступ слева плюс место для текстовых меток по координате х, а также при-
ближенную оценку на основе эскиза по координате у.
Далее определяются два важных значения: первое — расстояние на гистограмме,
изображающее единицу:
$bar_unit = ($width-($x+$right_margin)) I 100; // одна "точка" на гистограмме
Это максимальная длина полосы — от базовой линии до отступа справа — деленная
на 100, так как график отображает значения в процентах.
Глава 21. Генерация изображений
479
Второе значение — полная высота изображения:
Sheight = $num_candidates * ($bar_height + $bar_spacing) + 50;
Это высота одной полосы, умноженная на их количество, плюс место под заголо-
вок. Часть 3 сценария показана в листинге 21.5.3.
Листинг 21.5.3. showpoll .php — часть 3 сценария готовит все данные для вывода графика
/*******************************************
Создание базового изображения
*******************************************/
// Создание пустого холста
$im = imageCreateTrueColor($width,Sheight);
// Назначение цветов
$white=ImageColorAllocate($im,255,255,255);
$blue=ImageColorAllocate(Sim,0,64,128);
Sblack=lmageColorAllocate($im,0,0,0);
Spink = ImageColorAllocate($im,255,78,243);
$text_color = Sblack;
Spercent_color = Sblack;
$bg_color = $white;
$line_color = Sblack;
$bar_color = $blue;
$number_color = Spink;
// Создание фона для рисования
ImageFilledRectangle(Sim,0,0,$width,Sheight,Sbg_color);
// Контур фонового изображения
imageRectangle($im,0,0,$width-l,$height-l,Sline_color);
// Вывод заголовка
Stitle = 'Результаты голосования';
$title_dimensions = imageTTFBBox($title_size, 0, Sfont, Stitle);
$title_length = $title_dimensions[2] - $title_dimensions[0];
Stitle_height = abs($title_dimensions[7] - Stitle_dimensions[1]);
$title_above_line = abs($title_dimensions[7]);
$title_x = ($width-$title_length)/2; центрирование no x
$title_y = ($y - $title_height)/2 + Stitle_above_line; . центрирование по у
ImageTTFText($im, $title_size, 0, $title_x, $title_y,
$text_color, $font, Stitle);
// Вычерчивание базовой линии, начиная чуть выше первой .полоски
// и завершая чуть ниже последней
ImageLine($im, $х, $у-5, Sx, $height-15, $line color);
В части 3 выполняется подготовка базового изображения, назначение цветов и
вывод части графика.
На этот раз заливка фона осуществляется следующим образом:
ImageFilledRectangle(Sim,0,0,Swidth,Sheight,$bg_color);
Функция ImageFilledRectangle (), как можно предположить по названию, выво-
дит прямоугольник с заливкой. Как всегда, первый параметр — идентификатор изо-
бражения. За ним следуют координаты х и у начальной и конечной точек, то есть,
480
Часть IV. Усовершенствованные технологии РНР
соответственно, левого верхнего и правого нижнего углов прямоугольника. В данном
случае весь холст заливается цветом фона — белым, который является последним па-
раметром функции.
Затем происходит следующий вызов:
ImageRectangle ($im, 0,0, $width-l, $height-l, $line_color) ,-
который обеспечивает прорисовку контура по краю изображения. Эта функция вы-
водит прямоугольник с контуром и имеет такие же параметры. Обратите внимание,
что конечная точка имеет координаты $width-l и $height-l. Если бы они были рав-
ны Swidth и $height, прямоугольник вышел бы за пределы холста.
Для центрирования и вывода заголовка гистограммы применяется та же логика и
те же функции, что и в предыдущем сценарии.
И, наконец, выводится базовая линия:
ImageLine($im, $х, $у-5, $х, $height-15, $line_color),-
Функция ImageLineO вычерчивает линию цвета $line_color на заданном изо-
бражении $im, от точки ($х, $у-5) до точки ($х, $height-15).
В нашем случае базовая линия начинается чуть выше первой полосы и проходит
вниз почти до конца холста.
Теперь все готово к выводу данных. Часть 4 сценария показана в листинге 21.5.4.
Листинг 21.5.4. showpoll.php — часть 4 сценария выводит на графике подготовленные
данные и выполняет заключительные операции
/***t***i:*,J(*‘*****‘'***i‘ttti*t**ti'***jf***i:i'***i
Вывод данных в виде графика
// Получение всех данных из базы и отображение соответствующих полос
while ($row = $result->fetch_object())
{
if ($total_votes > 0)
Spercent = intval(($row->num_votes/$total_votes)*100);
else
Spercent = 0;
././ Вывод процентов для данного значения
$percent_dimensions = ImageTTFBBox($main_size, 0, $font, Spercent.;
$percent_length = $percent_dimensions[2] - $percent_dimensions[0];
ImageTTFText($im, $main_size, 0, $width-$percent_length-$text_indent,
$y+($bar_height/2), $percent_color, $font, Spercent.;
././ Длина полосы для данного значения
$bar_length = $х + (Spercent * $bar_unit);
/./ Вывод полосы для данного значения
ImageFilledRectangle($im, $х, $у-2, $bar_length, $y+$bar_height,
$bar_color);
// Вывод заголовка для данного значения
ImageTTFText($im, $main_size, 0, $text_indent, $y+($bar_height/2),
$text_color, $font, ''$row->candidate'') ;
Глава 21. Генерация изображений
481
// Прорисовка контура, соответствующего 100%
ImageRectangle($im, $bar_length+l, $у-2,
($х+(100*$bar_unit)), $y+$bar_height, $line„color,;
// Вывод чисел
ImageTTFText($im, $small_size, 0, $x+(100*$bar_unit)-50, $y+($bar_height 2,
$number_color, $font, $row->num_votes.'/'.$total_votes);
// Спуск к следующей полосе
$у=$у+($bar_height+$bar_spacing);
}
Вывод готового изображения
*******************************************/
Header('Content-type: image/png');
ImagePNG($im);
/О**************************************.**
Освобождение ресурсов
я*******,***,***,********,****,***********/
ImageDestroy ($im);
В части 4 сценария из базы данных поочередно выбираются данные для каждого
кандидата, вычисляется процент голосов, а затем выводятся полосы и поясняющие
надписи.
Как и ранее, текст выводится при помощи функции ImageTTFText (). Заполнен-
ные прямоугольники выводятся функцией ImageFilledRectangle ():
ImageFilledRectangle($im, $x, $y-2, $bar_length, $y+$bar_height, $bar_color);
Контуры, соответствующие 100%, выводятся функцией ImageRectangle ():
ImageRectangle($im, $bar_length+l, $y-2,($x+(100*$bar_unit)),
$y+$bar_height, $line_color)
После вывода всех полос изображение пересылается в браузер с помощью функ-
ции ImagePNGO, а затем посредством функции ImageDestroy () освобождаются все
ресурсы.
Хоть это довольно-таки объемный сценарий, тем не менее, его несложно адапти-
ровать под свои нужды. Важным свойством сценария является то, что в нем нет “про-
тивообманного” механизма. Пользователи смогут очень быстро обнаружить, что
можно проголосовать несколько раз, что сделает результаты, по сути, бессмыслен-
ными.
Если вы достаточно владеете математикой, то можете воспользоваться аналогич-
ным подходом для вывода линейных графиков или, скажем, секторных диаграмм.
Другие функции обработки изображений
Кроме графических функций, рассмотренных в этой главе, существуют и мно-
гие другие. Формирование графических изображений с помощью языка програм-
мирования требует длительного времени, а также многих проб и ошибок, пока
482
Часть IV. Усовершенствованные технологии РНР
получится хоть что-нибудь приемлемое. Создание любого изображения следует
начинать с макета на бумаге, а уже затем искать в руководстве описание нужных
для этого функций.
Дополнительные источники информации
Немало полезных материалов доступно в Internet. Если вы испытываете затрудне-
ния при использовании функций работы с изображениями, стоит обратиться к доку-
ментации по библиотеке GD, поскольку PHP-функции являются лишь оболочками
для этой библиотеки. Документация по GD находится по адресу:
http://www.boutell.сош/gd/
Не забывайте, однако, что версия GD2 встроена в главную библиотеку РНР, по-
этом}- некоторые детали могут отличаться.
Существуют также великолепные обучающие курсы по отдельным типам гра-
фических приложений на сайтах Zend и Devshed, доступные, соответственно, в
http://www.zend.com и http:I/devshed.com.
Идеи приложения, создающего гистограмму, основаны на сценарии Стива Маран-
ды (Steve Maranda), доступном на сайте Devshed.
Что дальше
В следующей главе рассматривается технология управления сеансами.
Глава 21. Генерация изображений
483
22
Управление сеансами
в РНР
В этой главе вы ознакомитесь с возможностями управления сеансами в РНР.
В главе, помимо прочих, рассматриваются следующие темы:
Что такое управление сеансами.
Cookie-наборы.
Настройка сеанса.
Переменные сеанса.
Сеансы и аутентификация.
Что такое управление сеансами
Возможно, вам доводилось слышать, что HTTP называют “протоколом без со-
стояния”. Это означает, что данный протокол не имеет встроенного способа под-
держки состояния между двумя транзакциями. Если пользователь запрашивает одну
страниц}', а затем другую, то с помощью HTTP невозможно установить, что оба за-
проса исходят от одного и того же пользователя.
Идея управления сеансами заключается в обеспечении отслеживания пользовате-
ля в течение одного сеанса связи с Web-сайтом. Если это удастся, мы сможем легко
обеспечить регистрацию пользователя и предоставление ему' информации в соответ-
ствии с его правами доступа или персональными настройками. Появится возмож-
ность отслеживать поведение пользователя. Кроме того, можно будет реализовать
концепцию покупательской тележки (shopping cart).
Начиная с четвертой версии, в РНР появились собственные встроенные функции
управления сеансами. Подход к управлению сеансами несколько изменился с вводом
суперглобальных переменных; теперь можно пользоваться суперглобальным масси-
вом $__SESSION.
Базовая функциональность сеансов
Для управления сеансами в РНР используется уникальный идентификатор сеанса,
представляющий собой зашифрованное случайное число. Он генерируется РНР и
сохраняется на стороне клиента на протяжении всего сеанса. Идентификатор сеанса
может либо храниться как cookie-набор на компьютере пользователя, либо переда-
ваться в составе URL.
Идентификатор сеанса играет роль ключа, обеспечивающего возможность реги-
страции некоторых специальных переменных в качестве так называемых перемен-
ных сеанса. Содержимое этих переменных сохраняется на сервере. Все, что видно на
стороне клиента — это идентификатор сеанса. Если во время некоторого подключе-
ния к вашему сайту вы можете “увидеть” идентификатор сеанса либо в cookie-наборе,
либо в URL, то можно получить доступ к переменным данного сеанса, которые хра-
нятся на сервере. По умолчанию переменные сеанса хранятся на сервере в обычных
текстовых файлах. (Можно написать собственную функцию, чтобы хранить пере-
менные в базе данных — более подробно об этом можно прочитать в разделе “Конфи-
гурирование управления сеансами”).
Скорее всего, вы уже имели дело с Web-сайтами, запоминающими идентификатор
сеанса в URL. Если в вашем URL присутствует строка каких-то случайных данных, то,
скорее всего, в этом случае используется одна из форм управления сеансами.
Другим решением проблемы сохранения состояния на протяжении нескольких
транзакций являются cookie-наборы, тогда URL-адрес не загромождается посторон-
ними данными.
Что такое cookie-набор?
Cookie-набор — это небольшой фрагмент информации, который сценарии сохра-
няют на клиентской машине. Чтобы установить cookie-набор на машине пользова-
теля, необходимо отправить ему HTTP-заголовок, содержащий данные в следую-
щем формате:
Set-Cookie: NAME=VALUE; [expires=OATE; 1 [path=PATff; ]
[domain=DOMAIN_NAME;] [secure]
В результате будет создан cookie-набор с именем NAME и значением VALUE. Все ос-
тальные параметры являются необязательными. В поле expires задается дата исте-
чения срока действия cookie-набора. (Заметим, что если дата истечения срока дейст-
вия не задана, cookie-набор будет постоянно действительным, пока его кто-нибудь не
удалит вручную — либо вы, либо сам пользователь.) Два параметра path и domain
применяются вместе для определения одного или нескольких URL, к которым отно-
сится данный cookie-набор. Ключевое слово secure означает, что cookie-набор не
должен пересылаться через простое НТТР-соединение.
Когда браузер соединяется с URL, он сначала просматривает cookie-наборы, хра-
нящиеся на локальной машине. Если какие-либо из них относятся к URL, с которым
установлено соединение, они передаются обратно серверу.
Установка cookie-наборов из РНР
Cookie-наборы в РНР можно установить вручную, воспользовавшись функцией
setcookie (). Она имеет следующий прототип:
bool setcookie (string name [, string value [, int expire [, string path
[, string domain [, int secure]]]]])
Глава 22. Управление сеансами в РНР
485
Параметры в точности соответствуют параметрам описанного выше заголовка
Set-Cookie.
Если cookie-набор установлен как
setcookie ('mycookie', 'value');
то когда пользователь заходит на следующую страницу вашего сайта (или переза-
гружает текущую страницу), вы получаете доступ к cookie-набору либо через
$_COOKIE['mycookie'], либо через $HTTP_COOKIE_VARS["mycookie"] (или же, если
включен режим register_globals, то напрямую через переменную $mycookie).
Для удаления cookie-набора необходимо вызвать setcookie () с тем же именем, но
с истекшим сроком действия. Для записи cookie-набора вручную можно воспользо-
ваться также функцией header () и описанным выше синтаксисом представления
cookie-набора. Однако при этом следует иметь в виду, что заголовки cookie-наборов
должны отправляться перед всеми другими заголовками, иначе они не будут работать.
(Это ограничение не РНР, а самих cookie-наборов.)
Использование cookie-наборов в сеансах
С cookie-наборами связаны некоторые проблемы: некоторые браузеры не прини-
мают cookie-наборы, кроме того, некоторые пользователи запрещают использование
cookie-наборов в браузерах, понимающих cookie. Это одна из причин, по которым в
PHP-сеансах используются двойной метод cookie-na6op/URL. (Ниже этот вопрос
рассматривается более подробно.)
При использовании PHP-сеансов нет необходимости устанавливать cookie-наборы
вручную. Функции сеанса делают это сами.
Чтобы просмотреть содержимое cookie-набора, установленного текущим сеансом,
можно использовать функцию session_get_cookie_params(). Она возвращает мас-
сив, содержащий элементы lifetime, path и domain и secure.
Можно воспользоваться также
session_set_cookie_params(^lifetime, $path, $domain [, $secure]);
для установки параметров cookie-набора сеанса.
Для получения более подробной информации о cookie-наборах следует обратить-
ся к спецификации cookie-наборов, которая доступна на сайте компании Netscape:
http://home.netscape.com/newsref/std/cookie_spec.html
(He обращайте внимания на то, что этот документ декларирован как “предвари-
тельное описание” — это тянется с 1995 года. К тому же данный документ по праву
считается стандартом, хотя формально не имеет такой формулировки.)
Сохранение идентификатора сеанса
В РНР cookie-наборы в сеансах используются по умолчанию. Если есть возмож-
ность установить cookie-наборы, то для сохранения идентификатора сеанса будет
применяться именно этот способ.
Другой метод заключается в добавлении идентификатора сеанса к URL-адресу.
Можно сделать так, чтобы идентификатор сеанса добавлялся к URL автоматически —
486
Часть IV. Усовершенствованные технологии РНР
для этого следует установить директиву session. use_trans_sid в файле php.ini. По
умолчанию эта директива отключена.
Можно поступить и по-другому — вручную встроить идентификатор сеанса в ссыл-
ку, чтобы обеспечить его передачу. Идентификатор сеанса будет запоминаться в кон-
станте SID. Для того чтобы передать его вручную, его потребуется добавить в конец
ссылки, аналогично параметру GET:
<А HREF="link.php?<?php echo strip_tags(SID); ?>">
(Функция strip_tags () здесь служит для того, чтобы сделать невозможными ата-
ки между сайтами с помощью сценариев.) Намного проще все же скомпилировать
РНР с опцией --enable-trans-sid.
Реализация простых сеансов
Основными этапами использования сеанса являются:
Запуск сеанса.
Регистрация переменных сеанса.
Использование переменных сеанса.
Разрегистрация (то есть отмена регистрации) переменных и закрытие сеанса.
Заметим, что эти этапы не обязательно должны содержаться в одном сценарии, и
некоторые из них могут находиться в нескольких сценариях. Рассмотрим последова-
тельно каждый из перечисленных этапов.
Запуск сеанса
Прежде чем можно будет воспользоваться возможностями, предоставляемыми се-
ансом, следует запустить сам сеанс. Существует два способа сделать это.
Первый (и самый простой) заключается в помещении в начало сценария вызова
функции session_start():
session_start();
Эта функция проверяет, находитесь ли вы все еще в текущем сеансе. Если нет, она
создает идентификатор сеанса, обеспечивая доступ к суперглобальному массиву
$_SESSION.
Неплохо помещать вызов session—start () в начало всех сценариев, в которых
используется механизм управления сеансами.
Второй способ запустить сеанс — задать установки РНР, при которых сеанс будет
запускаться автоматически, как только кто-то посетит ваш сайт. Для этого следует
воспользоваться опцией session.auto_start в файле php.ini. Более подробно ука-
занный способ будет описан при рассмотрении вопросов конфигурирования. С этим
методом связан один большой недостаток — при включенной опцим auto_start
нельзя использовать объекты в качестве переменный сеанса.
Регистрация переменных сеанса
Способ регистрации переменных сеанса в РНР недавно был изменен. Начиная с
версии РНР 4.1, переменные сеанса хранятся в суперглобальном массиве $_SESSION. а
Глава 22. Управление сеансами в РНР 487
также, по-старому, в массиве $HTTP_SESSION_VARS. Мы рекомендуем пользоваться
массивом $_SESSION. Для создания переменной сеанса вы просто устанавливаете в
этом массиве какой-то элемент, например:
$_SESSION['myvar'] = 5;
Если вы пользуетесь старой версией РНР, то чтобы иметь возможность переда-
вать переменные из одного сценария в другой, их необходимо зарегистрировать, вы-
звав функцию session_register (). Такой подход применять не рекомендуется.
Созданная переменная сеанса актуальна до тех пор, пока вы сеанс не будет завер-
шен либо пока она не будет явно разрегистрирована.
Использование переменных сеанса
Чтобы сделать переменную сеанса доступной для использования, сначала
необходимо запустить сеанс с помощью session_start(). После этого к пере-
менным можно обратиться через суперглобальный массив $_SESSlON, напри-
мер, так: $_SESSION['myvar'].
Если в качестве переменной сеанса используется некоторый объект, очень важно
поместить перед вызовом session_start() определение соответствующего класса,
чтобы можно было перегружать переменные сеанса. В результате РНР будет знать,
как реконструировать объект сеанса.
Если опция register_globals включена, к переменным можно иметь доступ с ис-
пользование их коротких имен, заданных в форме (скажем. $myvar), однако пользо-
ваться данным подходом не рекомендуется. Помните, что при включенной опции
register_globals переменные сеанса не могут быть перезаписаны данными, переда-
ваемыми методами GET или POST. Это хорошо с точки зрения безопасности, однако
сопряжено с некоторыми ограничениями при кодировании.
С другой стороны, следует проявлять аккуратность при проверке, установлены ли
переменные сеанса (с помощью, например, isset () или empty ()). Необходимо иметь
в виду, что переменные могут быть установлены пользователем с помощью GET или
POST. Проверить, зарегистрирована ли переменная как переменная сеанса, можно
через массив $_SESSION.
Проверить, зарегистрирована ли переменная, очень просто, например:
if (isset($_SESSION['myvar'])) ...
Разрегистрация переменных и уничтожение сеанса
После окончания работы с переменной сеанса ее можно разрегистрировать (от-
менить ее регистрацию). Это делается непосредственно через массив $_SESSION, на-
пример:
unset($_SESSION['myvar' ]);
Обратите внимание, что вызовы функций session_unregister () и
session_unset () более не требуются, да и не рекомендуются. Эти функции использо-
вались до введения массива $_SESSION.
488
Часть IV. Усовершенствованные технологии РНР
Не следует пытаться разрегистрировать целиком весь массив $_SESSION, посколь-
ку это отключит все сеансы. Для разрегистрации сразу всех переменных сеанса мож-
но воспользоваться следующим оператором:
$_SESSION = array();
По завершении сеанса сначала потребуется разрегистрировать все переменные, а
затем вызвать
session_destroy();
для очистки идентификатора сеанса.
Пример простого сеанса
Изложенный выше материал может показаться несколько абстрактным, поэтому
мы рассмотрим сейчас пример сеанса, работающего с тремя страницами.
На первой странице запускается сеанс и создается переменная
$_SESSION[' $sess_var' ]. Код, позволяющий сделать это, показан в листинге 22.1.
Листинг 22.1. pagel .php — запуск сеанса и создание переменной сеанса
<?php
session_start();
$__SESSION[ ' sess_var' ] = "Приветсвуем на нашем сайте!”;
echo 'Значение переменной $_SESSION[\'$sess_var\1] равно <b>1
. $_SESSION[ 1 sess_var1 ] . ' </bxbr /> ' ;
<а href=”page2,php”>Ha следующую страницу</а>
Этот сценарий создает переменную и устанавливает ее значение. Результат рабо-
ты сценария показан на рис. 22.1.
Рис. 22.1. Исходное значение переменной сеанса, отображае-
мое сценарием pagel. php
Глава 22. Управление сеансами в РНР
489
Конечное значение переменной на этой странице — это то значение, которое будет
доступно на последующих страницах. В конце сценария переменная сеанса сериа
лизируется (то есть преобразуется в последовательную форму), или замораживается,
до перезагрузки при следующем вызове session_start ().
Таким образом, следующий сценарий начинается с вызова session_start (). Этот
сценарий показан в листинге 22.2.
Листинг 22.2. page2 .php — доступ к переменной сеанса и ее разрегистрация
<?php
session_start();
echo 'Значение переменной $_SESSION[\'sess_var\'] равно <b>'
,$_SESSION['sess_var'].'</b><br />';
unset($_SESSION[1sess_var' ] ) ;
<a href = "радеЗ.php">Ha следующую страницу</а>
После вызова session_start() переменная $_SESSION['sess_var'] становится
доступной, а ее значением будет то, которое сохранено в предыдущем сеансе, что
можно видеть на рис. 22.2.
Рис. 22.2. Значение переменной сеанса было передано через
идентификатор сеанса странице page2 . php
Проделав над переменной все необходимые действия, ее нежно разрегистриро-
вать. Сеанс еще существует, но переменная $sess_var уже не будет существовать.
И, наконец, мы переходим к радеЗ - php, последнему сценарию в рассматриваемом
примере. Код этого сценария показан в листинге 22.3.
Листинг 22.3. радеЗ - php — завершение сеанса
<?php
session_start();
echo 'Значение переменной $_SESSION[\'sess_var\'] равно <b>'
. $_SESSION[ ' sess_var' ] . ' </bxbr /> ' ;
session_destroy();
490
Часть IV. Усовершенствованные технологии PHP
Как можно видеть на рис. 22.3, значение $_SESSION [' sess_var' ] более не доступно.
Рис. 22.3. Переменная сеанса больше не доступна
В версиях РНР до 4.3 при попытке разрегистрации элементов массивов
$HTTP_SESSION_VARS или $_SESSION может возникнуть ошибка. В этом случае при не-
возможности разрегистрировать элементы (то есть они остаются установленными)
нужно воспользоваться функцией session_unregister ().
Сценарий завершается вызовом функции session_aestroy(), которая уничтожа-
ет идентификатор сеанса.
Конфигурирование управления сеансами
Сейчас давайте ознакомимся с набором опций конфигурирования для сеансов, ко-
торые можно установить в своем файле php. ini. В табл. 22.1 перечислены некоторые
из наиболее полезных опций вместе с их кратким описанием.
Реализация аутентификации средствами
управления сеансами
В завершение рассмотрим более важный пример использования концепции
управления сеансами.
Пожалуй, чаще всего управление сеансами применяется для хранения информа-
ции о пользователях после их аутентификации через механизм входной регистрации.
В предлагаемом примере сочетаются аутентификация с помощью базы данных
MySQL и использование механизма управления сеансами. Эти возможности составят
основу проекта в главе 26, а впоследствии будут применяться и в других проектах.
В нашем примере будет использована база данных аутентификации, созданная в гла-
Глава 22. Управление сеансами в РНР
491
ве 16. Для освежения в памяти подробностей, касающихся этой базы данных, обрати-
тесь к листингу 16.3.
Таблица 22.1. Опции конфигурации сеансов
Имя опции По умолчанию Действие
session.auto_start 0 (запрещено) Автоматический запуск сеансов.
session.cache_expire 180 Установка времени жизни для кэшированных страниц сеанса (в минутах).
session.cookie_domain нет Домен для установки в cookie-наборе сеанса.
session.cookie_lifetime 0 Продолжительность действия cookie-набора идентификатора сеанса на машине пользователя. По умолчанию 0 — пока не будет закрыт браузер.
session.cookie_path / Путь для установки в cookie-наборе сеанса.
session.name PHPSESSID Имя сеанса, которое в системе пользователя ис- пользуется как имя cookie-набора.
Session.save_handler файлы Место хранения данных сеанса. Здесь можно указать базу данных, однако для этого потребует- ся реализовать собственные функции.
session.save_path / trap Путь к месту хранения данных сеанса. Выражаясь более обобщенно —аргумент, передаваемый об- работчику' session.save_handler.
session.use_cookies 1 (разрешено) Конфигурирует сеансы для использования cookie-наборов на стороне клиента.
Пример состоит из трех простых сценариев. Первый, authmain.php, обеспечива-
ет форму для входной регистрации и аутентификации пользователей Web-сайта.
Второй, members_only.php, предоставляет информацию только для тех пользовате-
лей, которые успешно прошли входную регистрацию. Третий, logout .php, реализует
выход пользователя из системы.
Чтобы понять, как все это работает, достаточно посмотреть на рис. 22.4. Это ис-
ходная страница, выводимая сценарием authmain.php.
492
Часть IV. Усовершенствованные технологии РНР
Рис. 22.4. Поскольку пользователь еще не зарегистрировался,
для него отображается страница входной регистрации
Данная страница предоставляет пользователю возможность зарегистрироваться и
войти в систему. Если он предпримет попытку перейти к разделу, предназначенному
только для авторизованных пользователей, без входной регистрации, будет выдано
сообщение, показанное на рис. 22.5.
Рис. 22.5. Не прошедшим входную регистрацию пользователям
не разрешен просмотр содержимого сайта — вместо этого они
увидят данное сообщение
Если же пользователь сначала регистрируется (с именем пользователя testuser и
паролем password, как было определено в главе 16), а потом попытается войти на
страницу, предназначенную только для авторизованных пользователей, он увидит
сообщение, показанное на рис. 22.6.
Глава 22. Управление сеансами в РНР
493
Рис. 22.6. После успешного входа пользователь может про-
смотреть части сайта, доступные только для зарегистрирован-
ных пользователей
Рассмотрим код этого приложения. Большая часть кода сосредоточена в сценарии
authmain.php, который показан в листинге 22.4. Давайте проанализируем его.
Листинг 22.4. authmain.php — главная часть приложения аутентификации
<?php
session_start();
if (isset($_POST['userid']) && isset($_POST('password']))
{
// Если пользователь как раз пытался зарегистрироваться
$userid = $_POST['userid'];
$password = $_POST['password'];
$db_conn = new mysqli('localhost', 'webauth', 'webauth', 'auth');
if (mysqli_connect_errno()) {
echo 'Невозможно подключиться к базе данных: '.mysqli_connect_error();
exit();
}
$query = 'select * from authorised_users '
."where name='$userid’ "
." and password=shal('$password')";
$result = $db_conn->query($query);
if ($result->num_rows > 0 )
{
// Если пользователь найден в базе данных, регистрируем его идентификатор
$_SESSION['valid-User'] = $userid;
}
$db_conn->close();
}
?>
<html>
494
Часть IV. Усовершенствованные технологии РНР
<body>
<Ы>Домашняя страница</Ы>
if (isset($_SESSION['valid_user']))
(
echo 'Вы вошли как '.$_SESSION['valid_user'].' <br />';
echo '<a href="logout.php">Выход</а><Ьг />';
}
else
{
if (isset($userid))
{
// Была предпринята неудачная попытка зарегистрироваться
echo 'Вход невозможен.<br />';
}
else
{
// Пользователь либо не пытался войти, либо уже вышел
echo 'Вы не вошли в систему.<br /><Ьг />':
}
// Форма для входа в систему
echo '<form method=''post" action="authmain.php">' ;
echo '<table>';
echo ' <trxtd>ta: </td> ' ;
echo '<td><input type="text” name="userid"x/tdx/tr>' ;
echo ' <trxtd>naponb :</td>';
echo '<tdxinput type="password" name="password"x/tdx/tr>' ;
echo '<tr><td colspan="2" align="center">';
echo '<input type="submit" value="Bxor"></tdx/tr>' ;
echo ' </tablex/form>' ;
}
<br>
<a href=''members_only.php">Pa3flen для зарегистрированных пользователей</а>
</body>
</html>
Логика данного сценария довольно-таки сложна, но без этого не обойтись, так как
здесь осуществляется вывод формы для входной регистрации и ее обработка, а также
содержится HTML-код для успешной и неудачной попыток аутентификации.
Работа этого сценария сосредоточена вокруг переменной сеанса $valid_user.
Основная идея заключается в следующем: если кто-либо успешно прошел
процедуру аутентификации, регистрируется переменная сеанса с именем
$_SESSION [' $valid_user' ], которая хранит идентификатор пользователя.
Первое, что выполняется в сценарии — вызов session_start (). Эта функция загру-
жает переменную сеанса $ val id_use г, если эта переменная была зарегистрирована.
При первом проходе по сценарию ни одно из условий if не выполняется, и управ-
ление сразу переходит в конец сценария, где выдается сообщение о том, что пользо-
ватель не вошел в систему, и отображается форма, с помощью которой он может это
сделать:
Глава 22. Управление сеансами в РНР
495
echo '<form method=”post" action="authmain.php">';
echo <table>1;
echo ' <trxtd>MMH: </td>' ;
echo '<tdxinput type="text" name="userid"x/tdx/tr>' ;
echo ' <trxtd>IIaponb : </td> ' ;
echo '<tdxinput type="password" name= "password"></tdx/tr>' ;
echo '<trxtd colspan="2" align="center">';
echo '<input type="submit” value="Вход”></tdx/tr>';
echo '</tablex/form>' ;
Когда пользователь щелкнет по кнопке отправки формы (с надписью Вход), сце-
нарий вызывается заново, и все повторяется с начала. На этот раз будут доступны имя
пользователя и пароль, позволяющие его аутентифицировать, которые хранятся в
$_POST [' userid' ] и $_POST [' password' ] ) Если эти переменные установлены, управ-
ление передается к блоку аутентификации:
if (isset($_POST['userid']) && isset($_POST['password']))
{
// Если пользователь как раз пытался зарегистрироваться
$userid = $_POST['userid'];
$password = $_POST['password'];
$db_conn = new mysqli('localhost', 'webauth', 'webauth', 'auth');
if (mysqli_connect_errno()) {
echo 'Невозможно подключиться к базе данных: '.mysqli_connect_error();
exit();
$query = 'select * from authorised_users '
."where name='$userid' "
." and password=shal('$password')";
$result = $db_conn->query($query);
Далее осуществляется подключение к базе данных MySQL, и проверяются имя
пользователя и пароль. Если в базе данных есть такая пара, создается переменная
$_SESSION[ 'valid_user' ], содержащая идентификатор для конкретного пользовате-
ля, — таким образом, мы всегда будем знать, кто вошел в систему.
if ($result->num_rows > 0 )
{
// Если пользователь найден в базе данных, регистрируем его идентификатор
$_SESSION['valid_user'] = Suserid;
}
$db_conn->close();
}
Поскольку теперь пользователь известен, то повторно предоставлять ему форму
входной регистрации нет необходимости. Вместо этого пользователю сообщается,
что мы знаем, кто он такой, и даем ему возможность при желании выйти из системы:
if (isset($_SESSION['valid_user']))
{
echo 'Вы вошли как '.$_SESSION)'valid_user'].' <br />';
echo '<a href="logout.php">Bbixofl</axbr />';
}
496
Часть IV. Усовершенствованные технологии PHP
Если же попытка войти у пользователя по какой-то причине потерпела неудачу', то у
нас имеется идентификатор пользователя, но нет переменной $_SESSION[ 'valid_user' ],
поэтому можно выдать сообщение об ошибке:
if (isset($userid))
{
// Была предпринята неудачная попытка зарегистрироваться
echo 'Вход невозможен.<Ьг />';
}
С главным сценарием покончено. Посмотрим теперь на страницу “только для за-
регистрированных пользователей”. Код этого сценария показан в листинге 22.5.
Листинг 22.5. members _оп1у .php — код раздела для зарегистрированных пользователей
в процедуре проверки Web-сайтом достоверности пользователя
<?php
session_start();
echo ’ <М>Только для зарегистрированных пользователей/М>' ;
// Проверить переменные сеанса
if (isset($_SESSION['valid_user']))
(
echo '<р>Вы вошли как 1.$_SESSION['valid_user'].'</p>';
echo '<р>Далее следует содержимое, предназначенное только для зарегистри-
рованных пользователей.</р>';
)
else
{
echo '<р>Вы не вошли в систему.</р>1;
echo '<р>Эта страница для вас недоступна.</р>';
}
echo '<а href="authmain.php">Ha главную страницу</а>';
Представленный код просто запускает сеанс и проверяет, зарегистрирован ли
пользователь в текущем сеансе, то есть проверяет, установлено ли значение
$_SESSION[1valid_user 1 ]. Если пользователь вошел в систему, отображается содер-
жимое сайта, предназначенное только для зарегистрированных пользователей, в
противном случае пользователю сообщается, что у него нет прав просматривать это
содержимое.
И в завершение рассмотрим сценарий logout.php, который осуществляет выход
пользователя из системы. Код сценария показан в листинге 22.6.
Листинг 22.6. logout .php — этот сценарий разрегистрирует переменную сеанса
и уничтожает сеанс
<?php
session_start () ,-
// сохранить для проверки, входил ли пользователь в систему
$old_user = $_SESSION['valid_user'];
unset($_SESSION['valid_user']);
session_destroy();
Глава 22. Управление сеансами в РНР
497
<html>
<body>
<Ь1>Выхсд</Ь1>
<?php
if (!empty($old„user))
{
echo 'Успешный выход.<br />';
}
else
{
// Если пользователь не входил в систему,
// но каким-то образом попал на эту страницу
echo 'Вы не входили в систему, потому и выходить из нее не нужно.<Ьг />';
}
9>
<а href="authmain.php">Ha главную страницу</а>
</body>
</html>
Приведенный код очень прост, но местами довольно-таки изворотлив. Мы запус-
каем сеанс, запоминаем старое имя пользователя, разрегистрируем переменную
$valid_user и завершаем сеанс. После этого мы выдаем пользователю сообщение,
смысл которого зависит от того, вышел ли он из системы, или даже не входил в нее.
Рассмотренный простой набор сценариев служит основой для многих проектов,
разработкой которых мы займемся в последующих главах.
Дополнительные источники информации
Дополнительную информацию о cookie-наборах можно найти по следующему
адресу:
http://home.netscape.com/newsref/std/cookie_spec.html
Что дальше
Очередная часть книги практически завершена. Однако прежде чем переходить к
проектам, мы кратко рассмотрим некоторые полезные мелочи РНР, которые до сих
пор еще не упоминались.
498
Часть IV. Усовершенствованные технологии РНР
23
Другие полезные
свойства
В этой главе рассказано о тех свойствах и функциях РНР, которые не подпадают
под какую-либо определенную категорию.
В главе, помимо прочих, рассматриваются следующие темы:
Использование магических кавычек.
Выполнение команд, содержащихся в строке, с помощью функции eval ().
Прекращение выполнения с помощью die и exit.
Сериализация переменных и объектов.
Получение информации об окружении РНР.
Временное изменение среды выполнения.
Загрузка расширений РНР.
Выделение цветом элементов исходного кода.
Использование РНР в командной строке.
Использование магических кавычек
Возможно, вы заметили, что использование символов кавычек (' и ") и символа
обратной косой черты (\) внутри строк требует соблюдения осторожности. Интер-
претатор РНР не поймет оператор, содержащий строку наподобие
echo "color = "#FFFFFF"
и выдаст сообщение о синтаксической ошибке. Чтобы вставить кавычки внутрь стро-
ки, необходимо, чтобы они отличались от кавычек, окаймляющих строку. Например,
и вариант
echo "color = '#FFFFFF'";
и вариант
echo 'color = "SFFFFFF"';
являются допустимыми.
Та же проблема возникает с пользовательским вводом, а также вводом и выводом
других программ.
Синтаксический анализатор MySQL точно так же не поймет запрос наподобие
insert into company values ('Объект 'Укрытие');
Мы уже рассматривали функции addslashes () и stripslashes (), которые заме-
няют на управляющие последовательности присутствующие в строке непарные сим-
волы кавычек, двойных кавычек, обратной косой черты и NULL.
В РНР имеется полезная возможность автоматически (“магически”) добавлять и
убирать управляющие символы косой черты. С помощью двух установок в файле
php.ini можно включить или выключить магические кавычки для данных, получен-
ных из GET, POST, cookie-наборов и других источников.
Значение директивы magic_quotes._gpc определяет, будут ли применяться маги-
ческие кавычки при операциях с GET, POST и cookie-наборами.
Если при включенной опции magic..quotes_gpc пользователь в форме введет
"Bob's Auto Parts", сценарий получит строку "Bob\'s Auto Parts", поскольку
символ кавычки автоматически модифицируется. Такое поведение может оказаться
весьма полезным, тем не менее, вы не должны забывать, что происходит, и удалять
символы косой черты перед выводом данных на экран. Хорошо, если разрабатывае-
мый вами код рассчитан на конкретный сервер, однако если код планируется распро-
странять. то придется в нем учитывать как включенную опцию magic_quotes_gpc,
так и выключенную.
Функция get_magic_quotes_gpc () возвращает 1 или 0 в зависимости от текущего
состояния magic..quotes_gpc. В основном она нужна для проверки, стоит ли вызы-
вать функцию stripslashes () для данных, полученных от пользователя.
Значение magic_quotes_runtime управляет использованием магических кавычек в
функциях, получающих информацию из баз данных и файлов.
Для определения текущего значения magic_quotes_runtime служит функция
get_magic_quotes_runtime (), также возвращающая 1 или 0. Магические кавычки
можно включить или выключить непосредственно внутри сценария с помощью
функции set_magic_quotes_runtime ().
По умолчанию опция magic_quotes_gpc включена, a magic_quotes_runtime — вы-
ключена.
Выполнение команд, содержащихся
в строке, с помощью функции eval ()
Функция eval () выполняет строку как РНР-код.
Например, вызов
eval ( "echo 'Приветствуем всех на нашем сайте!';" );
выполняет оператор, содержащийся в строке. Эта строка выведет точно то же, что и
echo 'Приветствуем всех на нашем сайте!';
Функция eval () может оказаться полезной во многих случах. Например, можно
сохранить фрагмент кода в базе данных, а затем прочитать и выполнить его с помо-
500
Часть IV. Усовершенствованные технологии РНР
щью eval (); можно сгенерировать код в цикле, а затем с помощью той же функции
eval () выполнить его.
Наиболее общее использование eval () связано с системой генерации шаблонов.
Смесь из HTML, РНР и простого текста можно загружать из базы данных. Система
генерации шаблонов затем форматирует это содержимое и применяет к нему eval ()
для выполнения РНР-кода.
Функцию eval () можно применять для обновления или корректировки сущест-
вующего кода. Если существует большой набор сценариев, требующих однотипных
изменений, можно (хотя это и не особенно эффективно) написать сценарий, кото-
рый будет загружать старый сценарий в строку, вносить изменения с помощью
regexp и затем запускать измененный сценарий с использованием eval ().
Возможна даже ситуация, когда пользователь вводит PHP-код в браузере и запус-
кает его на сервере.
Прекращение выполнения с помощью
die и exit
До сих пор в этой книге для останова выполнения сценария применялся оператор
exit. Он имеет следующий вид:
exit ;
и ничего не возвращает. Вместо этого оператора можно использовать его псевдоним
die().
Чтобы завершение программы было более информативным, функции exit()
можно передать параметр. Это позволит вывести сообщение об ошибке или запус-
тить другую функцию до останова сценария. Аналогичные возможности имеются в
языке Perl.
Вот один из примеров:
exit ( 1 Сценарий завершен' )
Однако чаще exit комбинируется с использованием операции OR с выражением,
которое может завершиться неудачей, например, открытие файла или подключение
к базе данных:
mysql_query($query) or die('Невозможно выполнить запрос');
Вместо того чтобы просто вывести сообщение, перед завершением сценария
можно запустить единственную функцию:
function err_msg()
{
echo 1 Номер ошибки MySQL: ';
echo mysql_error();
}
mysql_query($query) or die(err_msg());
Такой подход позволяет вывести пользователю причину, по которой сценарий за-
вершился неудачей, или, например, закрыть HTML-дескрипторы либо очистить не до
конца сгенерированную страницу в буфере.
Глава 23. Другие полезные свойства
501
Можно также отправить себе сообщение об ошибке по электронной почте, доба-
вить его в журнальный файл или сгенерировать исключение.
Сериализация переменных и объектов
Сериализация (serialization) представляет собой процесс превращения того, что
может хранить в себе переменная или объект РНР, в поток байтов, который можно
сохранить в базе данных или передавать от одной Web-страницы к другой. Без этой
возможности было бы трудно сохранить или переслать целиком содержимое массива
или объекта.
Полезность этого свойства несколько уменьшилась с появлением механизма
управления сеансами. Сериализация в основном используется для того, для чего те-
перь служит управление сеансами. На самом деле функции управления сеансами се-
риализируют переменные сеанса, чтобы сохранять их между НТТР-запросами.
Тем не менее, может возникнуть необходимость сохранить массив или РНР-
объект в файле или базе данных. В этом случае необходимо знать, как действуют две
функции: serialize() и unserialize().
Вызов функции serialize () имеет следующий вид:
$serial_object = serialize($my_object);
Чтобы понять, что происходит при сериализации, достаточно посмотреть на зна-
чение, возвращаемое этой функцией. Она превращает содержимое объекта или мас-
сива в строку.
Например, можно запустить функцию serialize () для простого объекта employee,
определенного следующим образом:
class employee
{
var $name;
var $employee_id;
J;
$this_emp = new employee;
$this_emp->name = 'Матроскин';
$this_emp->employee_id = 5324;
Если сериализировать этот объект и вывести результат в браузер, то получим:
0:8:"employee":2:{s:4:"name”; S: 9:"Матроскин";s:11:"employee_id“;i:5324;)
Взаимосвязь между исходным объектом и сериализованными данными очевидна.
Поскольку сериализованные данные представляют собой обычный текст, его
можно записать в базу данных или распорядиться ним любым другим образом. Перед
записью в базу данных следует воспользоваться функцией addslashes (), поскольку
сериализованная строка изобилует кавычками.
Чтобы снова получить объект, необходимо воспользоваться обратной функцией —
unserialize():
$new_object = unserialize($serial_object);
Очевидно, что если перед помещением объекта в базу данных использовалась
функция addslashes (), то теперь следует вызвать функцию stripslashes ().
502
Часть IV. Усовершенствованные технологии РНР
При сериализации объектов или их использовании в качестве переменной сеанса
необходимо помнить, что РНР для восстановления экземпляра класса нужна инфор-
мация о его структуре. Значит, в тексте сценария перед вызовом session_start ()
или unserialize () должен быть включен файл с определением класса.
Получение информации об окружении РНР
Для выдачи информации о том, как сконфигурирован РНР, существует несколько
функций.
Определение загруженных расширений
Доступные наборы функций, а также конкретные функции в каждом из этих
наборов легко определить с помощью функций get_loaded_extensions() и
get_extension_funcs().
Функция get_loaded_ extensions () возвращает массив, содержащий все наборы
функций, доступные РНР в текущий момент. Если имя конкретного набора или рас-
ширения передать в качестве параметра функции get_extension_funcs (), она воз-
вратит массив имен функций в этом наборе.
С помощью этих двух функций сценарий, приведенный в листинге 23.1, выводит
список всех функций, доступных в текущей инсталляции РНР.
Листинг 23.1. list_functions -php — этот сценарий выводит все расширения, доступные
РНР, а для каждого расширения — маркированный список содержащихся в нем функций
<?php
echo 'Наборы функций, доступные в данной инсталляции:<Ьг />';
$extensions = get_loaded_extensionsО;
foreach ($extensions as $each_ext)
(
echo "$each_ext <br />":
echo '<ul>';
$ext_funcs = gec_extension_funcs($each_ext);
foreach($ext_funcs as $func)
echo "<li> $func </li>";
}
echo '</ul>';
)
?>
Обратите внимание, что функция get_loaded_extensions () вообще не принима-
ет параметров, а функции get_extension_funcs () нужен только один параметр —
имя расширения.
Подобная информация может оказаться полезной, если необходимо определить,
успешно было ли установлено расширение, либо когда вы разрабатываете переноси-
мый код, который генерирует полезные информативные сообщения во время своей
инсталляции.
Глава 23. Другие полезные свойства
503
Определение владельца сценария
Определить пользователя, являющегося владельцем запущенного сценария, мож-
но с помощью функции get_current_user ():
echo get_current_user();
Такая информация может понадобиться при разрешении вопросов с правами
доступа.
Определение даты последнего изменения сценария
Сейчас очень модно помещать на каждую страниц}' сайта дату ее последней моди-
фикации.
Для определения даты последнего изменения сценария используется функция
getlastmod() (обратите внимание на отсутствие символов подчеркивания в имени
функции):
echo date('g:i a, jMY', getlastmod());
Функция возвращает метку времени Unix, которую можно передать в функцию
date (), чтобы преобразовать ее в более читабельный формат.
Динамическая загрузка расширений
Если библиотеки расширений не скомпилированы при инсталляции, их можно за-
гружать во время выполнения, используя функцию dl (). Параметром функции явля-
ется имя библиотечного файла. В Unix имена таких файлов заканчиваются расшире-
нием . so, а в Windows — расширением . dll.
Ниже показан пример вызова функции dl ():
dl(1php_gd2.dll');
Этот вызов динамически загружает расширение gd2 (генерация изображений) на
машине, работающей под управлением Windows.
Каталог, в котором содержится файл с динамически загружаемой библиотекой,
здесь не указывается: он должен быть задан в конфигурационном файле php. ini с
помощью директивы extension_dir.
Если при динамической загрузке расширений возникают проблемы, необходимо
проверить директиву enable_dl в файле php.ini. Если она отключена, то динамиче-
ская загрузка расширений запрещена. Эта директива можег быть отключена по сооб-
ражениям безопасности, например, если на машине работаете не только вы. Функ-
цию dl () также нельзя использовать, если РНР выполняется в безопасном режиме.
Временное изменение среды исполнения
Набор директив в файле php. ini можно просматривать и изменять на время вы-
полнения сценария. Это может весьма пригодиться, например, совместно с директи-
вой max_execution_time, когда необходимо увеличить допустимое время выполне-
ния сценария.
504
Часть IV. Усовершенствованные технологии РНР
Просматривать и изменять директивы можно с помощью функций ini_get () и
ini_set (). Пример использования этих функций показан в простом сценарии в лис-
тинге 23.2.
Листинг 23.2. iniset.php — этот сценарий изменяет значения директив из файла php. ini
<?php
$old_max_execution_time = ini_set('max_execution_time1, 120);
echo "Старый лимит времени: $old_max_execution_time <br />";
Smax_execution_time = ini_get('max_execution_time');
echo "Новый лимит времени: $max_execution_time <br />";
Функция ini_set() принимает два параметра. Первый — это имя изменяемой
конфигурационной директивы из файла php.ini, а второй — ее новое значение.
Функция возвращает предыдущее значение этой директивы.
В данном случае значение максимального времени выполнения сценария вместо
30 секунд по умолчанию (или другого значения, установленного в php.ini) устанав-
ливается равным 120 секундам.
Функция ini_get () просто возвращает значение директивы, имя которой переда-
ется в параметре как строковое выражение. В сценарии эта функция используется
только для проверки того, что значение директивы действительно изменилось.
Таким способом могут быть установлены не все INI-опции. С каждой опцией свя-
зан уровень, на котором она может быть установлена. Ниже перечислены возможные
уровни:
PHP_INI_USER — эти значения можно изменять в сценариях с помощью
ini_set().
PHPINI—PERDIR — эти значения можно изменять в файлах php.ini либо
.htaccess или httpd.conf, если используется Apache. Факт, что вы можете из-
менять их в файлах .htaccess, означает возможность модификации значений
на уровне каталогов (отсюда и название).
PHP_INI_SYSTEM — эти значения можно изменять в файлах php.ini или
httpd. conf.
PHP_INI_ALL — эти значения можно изменять в любом из перечисленных выше
мест, то есть, в сценарии, в файле .htaccess и в файлах httpd.conf или
php.ini.
Полный список INI-опций и уровней, на которых они могут быть изменены, дос-
тупен в руководстве РНР по адресу http: / /www.php.net/ini_set.
Выделение цветом элементов
исходного кода
В состав РНР входит система выделения цветом синтаксиса, подобная таковым во
многих интегрированных средах разработки. Она очень удобна для передачи кода
другим разработчикам или опубликования его на Web-странице для обсуждения.
Глава 23 Другие полезные свойства
505
Функции show_source () и highlight_file() идентичны. (На самом деле
show_source () является псевдонимом функции highlight_file ().) В качестве пара-
метра обеим функциям передается имя файла. (Этот файл должен содержать РНР-
код, иначе получится бессмысленный результат.) Рассмотрим пример:
show_source('list_functions.php');
Переданный функции файл отображается в браузере, причем фрагменты текста
выделяются различными цветами в зависимости от того, являются ли они строкой,
комментарием, ключевым словом или HTML-дескриптором. Все это выводится на
фоне заданного цвета. Содержимое, не подпадающее ни под одну из перечисленных
категорий, выводится цветом по умолчанию.
Функция highlight_string () работает аналогично, но ее аргументом является
строка, а результатом — вывод в браузере в формате с выделенным синтаксисом.
Цвета для выделения синтаксиса можно установить в файле php.ini. Соответст-
вующий раздел файла выглядит следующим образом:
; Colors for Syntax Highlighting mode
; Цвета для режима выделения синтаксиса
highlight.string = #DD0000
highlight.comment = #FF9900
highlight.keyword = #007700
highlight.bg = #FFFFFF
highlight.default = #0000BB
highlight.html = #000000
Цвета определяются в стандартном для HTML RGB-формате.
Использование РНР в командной строке
Многие короткие программы можно выполнять в командной строке. В среде Unix
такие программы обычно написаны на языке сценариев оболочки, а в среде Windows
они имеют вид командных файлов.
Возможно, вы обратились к РНР из-за необходимости выполнения проектов для
Web, однако те возможности, которые делают РНР мощным инструментом для Web.
позволяют рассматривать его и как полезную утилиту командной строки.
Существуют три способа запуска PHP-сценария из командной строки: из файла,
через канал и путем ввода непосредственно в командной строке.
Для выполнения PHP-сценария, которых хранится в файле, необходимо убедить-
ся, что путь к исполняемому файлу PHP-интерпретатора (php или php. ехе, в зависи-
мости от операционной системы) находится в пути поиска, а затем вызвать его и ука-
зать имя файла сценария в качестве параметра. Например:
php myscript.php
Файл myscript.php представляет собой обычный PHP-сценарий, то есть содер-
жит нормальные операторы внутри дескрипторов РНР.
Для передачи кода через канал необходимо запустить любую программу, которая
генерирует PHP-код, и передать ее выходные данные по каналу в исполняемый файл
php. В следующем примере с помощью команды echo генерируется однострочная
программа, которая затем выполняется:
506
Часть IV. Усовершенствованные технологии РНР
echo '<?php for($i=l; $i<10; $i++) echo $i; ?>' | php
И здесь PHP-код должен быть заключен в пару дескрипторов РНР (<?php и ?>).
Также обратите внимание, что в приведенном примере echo — это Unix-программа, а
не языковая конструкция РНР.
Короткие однострочные программы пройде передавать непосредственно в ко-
мандной строке, как показано ниже:
php -г 'for($i=l; $i<10; $i++) echo $i;'
Здесь ситуация несколько иная. PHP-код, передаваемый в строке, не заключен в
дескрипторы РНР. Если вы попытаетесь поместить этот код между <?php и ?>, воз-
никнет синтаксическая ошибка.
Количество полезных PHP-программ, которые можно записать в командной стро-
ке, практически не ограничено. Можно создать программы установки разработанных
вами PHP-приложений. Вы можете на скорую руку набросать сценарий, формати-
рующий текст перед его помещением в базу данных. Вы можете даже подготовить
сценарий, выполняющий множество повторяющихся задач, которые вам приходится
выполнять ежедневно в командной строке; хорошим кандидатом на оформление в
виде сценария командной строки может быть копирование всех PHP-файлов, файлов
изображений и структур таблиц MySQL из Web-сервера, предназначенного для разра-
ботки, на производственный Web-сервер.
Что дальше
В части V рассматриваются несколько относительно сложных проектов, реализо-
ванных с использованием РНР и MySQL. Они представляют собой полезные приме-
ры решения широко распространенных задач и демонстрируют применение РНР и
MySQL для разработки больших проектов.
В главе 24 изложены основные вопросы, возникающие при создании больших
проектов на РНР, в том числе такие принципы разработки программного обеспече-
ния, как проектирование, документирование и управление изменениями.
Глава 23. Другие полезные свойства
507
V
Реальные проекты
на РНР и MySQL
Глава 24. Глава 25. Глава 26. Использование РНР и MySQL в крупных проектах Отладка Реализация задачи аутентификации и персонализации посетителей
Глава 27. Глава 28. Глава 29. Глава 30. Глава 31. Глава 32. Разработка покупательской тележки Разработка системы управления содержимым Разработка почтовой Web-службы Разработка диспетчера списков рассылки Разработка приложения поддержки Web-форумов Генерация персонифицированных документов в PDF-формате
Глава 33. Подключение к Web-службам с помощью XML и SOAP
24
Использование РНР и
MySQL в крупных проектах
В предыдущих частях книги обсуждались различные компоненты и случаи ис-
пользования РНР и MySQL. Мы старались сделать все примеры интересными и
актуальными, однако они довольно просты и включали в себя один, два, реже три
сценария длиной в какую-нибудь сотню строк кода.
Разработка реальных Web-приложений редко бывает настолько простой. Еще не-
сколько лет назад “интерактивный” Web-сайт предоставлял возможность отправки
формы по электронной почте, и это рассматривалось как нечто из ряда вон выходя-
щее. В наши дни Web-сайты превратились в Web-приложения, другими словами, ста-
ли настоящими программными средствами, доступными через Web. В результате
вместо коротких сценариев Web-сайты содержат многие тысячи строк кода. Проекты
подобного масштаба требуют тщательного планирования и управления в такой же
степени, как и проекты по разработке любых других программных систем.
Прежде чем приступить к обзору проектов, рассмотрим некоторые технологии
управления крупными Web-проектами. Это постоянно развивающееся искусство, и,
как показывает изучение рынка, постигнуть его не так-то просто.
В главе, помимо прочих, рассматриваются следующие темы:
Применение методов проектирования программного обеспечения при разра-
ботке Web-приложений.
Планирование и сопровождение проекта Web-приложения.
Повторное использование кода.
Написание удобного в сопровождении кода.
Управление версиями.
Выбор среды разработки.
Документирование проектов.
Моделирование.
Разделение логики, содержимого и представления: РНР, HTML и CSS.
Оптимизация кода.
510
Часть V. Реальные проекты на РНР и MySQL
Применение методов проектирования
программного обеспечения при разработке
Web-приложений
Возможно, вам уже известно, что проектирование представляет собой примене-
ние методов систематизации и количественных измерений к разработке программ-
ных средств. Другими словами, это приложение принципов проектирования в отно-
шении разработки программ.
Отметим, что данный подход явно отсутствует во многих Web-проектах, и это вы-
звано двумя основными причинами. Во-первых, разработка Web-приложений зачас-
тую напоминает создание какого-то отчета. Она предполагает построение структуры
документа, графическое оформление и публикацию. Такой подход ориентирован на
документ. Он вполне приемлем для статических сайтов малых и средних размеров.
Однако, с возрастанием динамического содержимого Web-сайтов до уровня, когда
они предоставляют не столько документы, сколько службы, данный принцип стано-
вится непригодным. Многим вовсе не приходит в голову воспользоваться принципа-
ми проектирования программного обеспечения.
Вторая причина состоит в том, что условия разработки Web-приложений во
многом отличаются от разработки обычных программных систем. Работа ведется
в исключительно сжатые сроки и под постоянным прессингом, дескать, создать
сайт следует немедленно. Ведение проектов обычных программ предполагает после-
довательность и методичность, а на планирование специально выделяется время,
причем немалое. При разработке Web-проектов часто господствует ощущение, что на
планирование вообще нет времени.
Отсутствие планирования Web-проектов приводит к таким же результатам, как и
при отсутствии планирования для разработки любых других программных систем:
ошибки в коде, нарушение ранее оговоренных сроков и код, совершенно не удобный
для чтения.
Трудность заключается в выборе методов сопровождения проектов программного
обеспечения, пригодных для разработки Web-приложений, и отказе от применения
всех остальных методов.
Планирование и сопровождение
проекта Web-приложения
Универсального метода планирования жизненного цикла Web-проектов не суще-
ствует. Однако имеется ряд моментов, которые должны быть учтены. Ниже приво-
дится их перечень, а более подробное обсуждение содержится в последующих разде-
лах. Необязательно следовать этим рекомендациям в порядке их изложения, если это
не особенно подходит к определенному проекту. Главное здесь заключается в том,
чтобы иметь представление о данных вопросах и выбирать рекомендации, примени-
мые к конкретному случаю.
Для начала следует тщательным образом продумать конечную цель создаваемо-
го продукта. Необходимо предельно четко уяснить конечные цели. Многие
Глава 24. Использование РНР и MySQL в крупных проектах
511
технически совершенные Web-проекты с треском провалились именно потому,
что никто не проверил, существуют ли пользователи, заинтересованные в при-
ложениях подобного рода.
Постарайтесь разбить приложение на отдельные компоненты. Каковыми будут
этапы разработки приложения? Как будет действовать каждый компонент? Как
компоненты будут взаимно дополнять друг друга? Здесь помогут сценарии, эс-
кизы и даже случаи использования.
После составления списка компонентов необходимо выяснить, какие из них
уже существуют. Если ранее созданный модуль обладает требуемыми функция-
ми, возможно, имеет смысл воспользоваться именно им. Не забывайте искать
готовый код как в своей организации, так и за ее пределами. В частности, со-
общество открытого исходного кода (Open Source community) бесплатно пред-
лагает великое множество компонентов. Определите, какой код придется соз-
давать с нуля, и, приближенно, насколько трудоемкой окажется эта задача.
Продумайте организацию самого процесса разработки. В Web-проектах этим
шагом зачастую пренебрегают. Здесь подразумеваются стандарты написания
кода, структура каталогов, управление версиями, среда разработки, уровень и
стандарты документирования, а также распределение задач между членами
группы разработчиков.
На основе ранее изложенных соображений постройте модель. Продемонстри-
руйте ее пользователям. Внесите, если необходимо, в модель требуемые изме-
нения.
Помните, что на всех этапах важно разделять содержимое и логику приложе-
ния. Эта идея более подробно рассматривается далее в главе.
Выполните необходимую оптимизацию.
Выполняйте тестирование настолько же тщательно, как и в отношении любого
другого программного проекта.
Многократное использование кода
Программисты часто по ошибке или неведению создают код. который уже суще-
ствует. Если известны необходимые компоненты приложения либо хотя бы необхо-
димая функциональность, перед тем как приступить к разработке, проверьте, что
имеется в наличии.
Иногда программисты повторно реализуют функции просто потому, что не удо-
сужились как следует прочитать руководство, а на самом деле имеются функции, ко-
торые предоставляют необходимые возможности. Всегда обеспечивайте возмож-
ность быстрого вызова руководства в интерактивном режиме. Однако имейте в виду,
что интерактивное руководство обновляется довольно часто. Кроме того, такое ру-
ководство содержит ссылки, что делает его великолепным ресурсом с комментария-
ми, рекомендациями и примерами кода, предоставленными другими пользователями.
Оно часто содержит ответы на вопросы, которые обычно возникают после чтения
основной страницы руководства, а также отчеты об ошибках и пути их обхода до то-
го, как ошибки будут исправлены или документированы.
512
Часть V. Реальные проекты на РНР и MySQL
Руководство по РНР на русском языке доступно по адресу:
http://www.php.net/manual/ru/
Первоисточник руководства на английском языке находится по адрес}7:
http://www.php.net/manual/en/
Некоторые неанглоязычные программисты поддаются искушению писать функ-
ции-оболочки, которые фактически присваивают PHP-функциям новые имена на
родном для разработчика языке. Эту практику часто называют ‘‘синтаксическим саха-
ром” (“syntactic sugar”). Так делать не рекомендуется — это усложняет чтение и сопро-
вождение кода другими программистами. Если вы изучаете новый язык, учитесь пра-
вильно его использовать. Кроме того, подобное добавление дополнительного уровня
вызова функций замедляет выполнение кода. С учетом всех обстоятельств, подобного
подхода следует избегать.
Если выяснилось, что необходимые функциональные возможности не обеспечи-
ваются базовой библиотекой РНР, существуют два пути. Для простых задач имеет
смысл разработать собственную функцию или объект. Однако при написании доста-
точно сложного кода, такого как покупательская тележка, система электронной поч-
ты для Web или Web-форум, нередко обнаруживается, что работа уже кем-то продела-
на. Одно из преимуществ работы с сообществом открытого исходного кода состоит в
том, что код компонентов приложений такого типа, как правило, распространяется
совершенно бесплатно. Если найден компонент, похожий на требуемый, пусть даже и
не полностью совпадающий, можно просмотреть исходный код и на его основе вы-
полнить модификацию или написать собственную программ}7.
После завершения разработки собственных функций или компонентов имеет
смысл всерьез задуматься над тем, чтобы предоставить их сообществу разработчиков
на РНР. Именно данный принцип и делает сообшество разработчиков на РНР таким
полезным, активным и информированным.
Написание удобного в сопровождении кода
Проблема сопровождения кода в Web-приложениях зачастую игнорируется.
Обычно это происходит по причине поспешного написания кода. Быстро начать и
завершить работу иногда представляется более важным, нежели выполнять предва-
рительное планирование. Однако небольшие затраты времени в начале могут сэко-
номить много времени в дальнейшем, когда потребуется создавать следующие версии
приложения.
Стандарты написания кода
Большинство крупных организаций, работающих в сфере информационных тех-
нологий, устанавливают собственные стандарты кодирования — правила именования
файлов и переменных, написания комментариев, применения выравнивания в коде и
прочие моменты.
Поскольку в недалеком прошлом разработка Web-приложений основывалась на
понятии документа, стандартами кодирования в этой области нередко пренебрегали.
Когда код пишется самостоятельно либо силами небольшой группы программистов.
Глава 24. Использование РНР и MySQL в крупных проектах
513
значение стандартизации очень легко недооценить. Однако так не следует поступать,
поскольку состав рабочей группы и масштаб проекта могут возрасти. В конечном ито-
ге получится некий хаос, и программисты попросту не смогут разобраться в сущест-
вующем коде.
Определение правил именования
Цели выработки правил именования заключаются в следующем:
Сделать код простым для восприятия. Осмысленное именование переменных
и функций позволяет читать код почти как обычный текст или, минимум, псев-
докод.
Упростить запоминание идентификаторов. Если идентификаторы сформиро-
ваны единообразно, будет проще вспомнить название определенной перемен-
ной или функции.
Имена переменных должны описывать данные, которые они содержат. Если в пе-
ременной хранится фамилия, назовите ее $ surname. Необходимо найти оптимальное
соотношение между краткостью и читабельностью имен. Например, сохранение
имени в переменной $п упростит набор кода, однако усложнит его понимание. Имя
$surname_of_the_current_user (фамилия текущего пользователя) более информа-
тивное, но неоправданно длинное (неудобно набирать, к тому же выше вероятность
допустить опечатку при наборе).
Необходимо выработать правила использования регистров символов. Как уже
упоминалось, в РНР имена переменных зависят от регистра. Потребуется решить,
как будут записываться имена переменных: в нижнем регистре, верхнем регистре
либо в их комбинации. Например, можно принять, что первые буквы слов будут про-
писными. Мы предпочитаем использовать только нижний регистр, поскольку это
проще для запоминания.
Неплохо использовать регистр для различения переменных и констант. Обычно
для переменных используются символы нижнего регистра (например, $result), а для
констант — верхнего (например, PI).
Некоторые программисты присваивают двум переменным одно и то же имя, но с
символами разного регистра, например, $name и $Name. Мы полагаем, что не стоит
объяснять, почему брать на вооружение подобную практику не рекомендуется.
Лучше избегать сложных схем применения регистров, например, $WaReZ. Они
достаточно трудны для запоминания.
Кроме того, стоит выбрать конструкцию имен переменных, состоящих из не-
сколько слов. Нам доводилось встречаться со всеми перечисленными ниже конструк-
циями:
$username
$user_name
$userName
Совершенно не важно, какой из них будет отдано предпочтение, главное — при-
менять ее последовательно. Имеет также смысл придерживаться разумных ограниче-
ний в количестве слов — не более двух-трех.
Для имен функций применимы многие из рассмотренных соображений, но только
с несколькими отличиями. Имена функций обычно основываются на глагольных
514
Часть V. Реальные проекты на РНР и MySQL
формах. Например, следующие имена встроенных PHP-функций описывают дейст-
вия, выполняемые над передаваемыми параметрами: addslashes () (добавить обрат-
ные косые черты) и mysqli_connect () (подключиться к MySQL). Это существенно
упрощает восприятие кода. Обратите внимание, что в приведенных выше именах
функций применены различные схемы именования переменных с использованием
нескольких слов. В этом отношении PHP-функции непоследовательны. В какой-то
мере это связано с тем, что они разрабатывались большой группой программистов,
однако в основном это объясняется тем, что многие имена функций адаптированы
без изменений из других языков или API-интерфейсов.
Кроме того, следует отметить, что в РНР имена функций не зависят от регист-
ра. Тем не менее, во избежание путаницы лучше придерживаться какого-то опре-
деленного формата.
Иногда применяют модульную схему именования, которая встречается во многих
PHP-модулях; при этом имя модуля служит префиксом имени функции. Например,
имена всех функций усовершенствованного модуля MySQL начинаются с префикса
mysqli_, функций IMAP — с префикса imap_. Так, для функций модуля покупатель-
ской тележки (shopping cart) имеет смысл использовать префикс cart_.
Обратите, однако, внимание, что имена функций отличаются в объектно-
ориентированном и процедурном интерфейсах РНР. Обычно имена функций из про-
цедурного интерфейса содержат символы подчеркивания (_), тогда как в именах
функций из объектно-ориентированного интерфейса используются прописные сим-
волы (это называют studlyCaps), как в myFunction().
В заключение отметим, что сами по себе правила именования большой роли не
играют. Важно лишь последовательно придерживаться выбранной схемы.
Комментирование кода
Все программы в разумных пределах должны снабжаться комментариями. Может
возникнуть вопрос, каковы эти “разумные” пределы. Обычно имеет смысл добавлять
комментарии к каждому из следующих элементов:
Файлам, будь то завершенные сценарии либо включаемые файлы. Коммента-
рии к каждому файлу должны информировать о его назначении, авторе и вре-
мени обновления.
Функциям. Необходимо указать, какие действия функция выполняет, какие
данные вводятся и что возвращается.
Классам. Следует описать назначение класса. Методы классов должны сопро-
вождаться комментариями того же типа и уровня, что и все другие функции.
Блокам кода внутри сценария или функции. Мы часто начинаем писать сце-
нарий с набора комментариев в стиле псевдокода, а затем заполняем кодом ка-
ждый раздел. Например, на начальном этапе сценарий может иметь следую-
щий вид:
<?
// Проверить входные данные
// Отправить информацию в базу данных
// Выдать результаты
Глава 24. Использование РНР и MySQL в крупных проектах
515
Это довольно удобно, поскольку после заполнения всех разделов код уже будет
содержать комментарии.
Сложным элементам кода. Если какой-то фрагмент кода потребовал долгих
раздумий либо содержит хитрый трюк, стоит написать к нему пояснение. Тогда
при последующем просмотре кода не придется, наморщив лоб, вспоминать:
“Что бы это могло значить?”.
Следующая общая рекомендация состоит в том, чтобы писать комментарии в про-
цессе работы над кодом. Не стоит рассчитывать на возможность вернуться к коду по-
сле завершения работы над проектом и только затем внести в него комментарии. Мы
уверены, что это никогда не удается, если только у вас не окажется намного менее
напряженный график работ и гораздо больше самодисциплины, нежели v нас.
Выравнивание кода
В любом языке программирования необходимо осмысленное и единообразное
применение выравнивания (то есть отступов) при оформлении кода. Это напоминает
форматирование резюме или делового письма. Выравнивание существенно улучшает
восприятие кода.
Обычно блок программы, который принадлежит некоторой управляющей струк-
туре, выделяется отступом относительно окружающего кода. Отступ должен быть
хорошо заметным (более одного пробела), но не слишком большим. Обычно мы про-
тив применения табуляции. Несмотря на то что это ускоряет печать, тем не менее, на
многих мониторах отнимает изрядное экранное пространство. Во всех проектах мы
используем отступы размером в два или три пробела.
Внимания также заслуживает и расположение фигурных скобок. Ниже показаны
два распространенных варианта:
Вариант 1:
if (condition) {
// какая-то обработка
)
Вариант 2:
if (condition)
{
// еще какая-то обработка
)
Какой из них использовать — дело сугубо личного вкуса. И снова стоит упомянуть,
что, во избежание неразберихи, выбранный стиль должен последовательно приме-
няться на протяжении всего проекта.
Фрагментирование кода
Монолитный код огромных размеров крайне неудобен. Некоторые программисты
создают один большой сценарий, который в одной главной строке кода выполняет
буквально все операции. Намного лучше разбить код на функции и/или классы, а
также поместить взаимосвязанные элементы в подключаемые классы. Например,
имеет смысл поместить все функции, связанные с базами данных, в файл с именем,
скажем, dbfunctions .php.
516
Часть V. Реальные проекты на РНР и MySQL
Ниже перечислены преимущества логического разбиения кода на блоки:
Упрощается чтение и понимание кода.
Улучшаются возможности многократного использования кода, и сводится к
минимуму его избыточность. Например, упомянутый выше файл dbfunc-
tions.php можно будет использовать в любом сценарии, где требуется под-
ключение к базе данных. Если необходимо внести изменения в процесс под-
ключения, достаточно это выполнить лишь в одном файле.
Создаются предпосылки для совместного труда целой команды разработчиков.
Когда код разбит на компоненты, можно возложить ответственность за разра-
ботку каждого из них на того или иного члена команды. Кроме того, исключа-
ется ситуация, когда одному программисту для продолжения работы прихо-
дится ожидать, пока коллега завершит работу над файлом GiantScript.php
(“Гигантский сценарий”).
На начальном этапе работы над проектом необходимо уделить время разбиению
проекта на компоненты, которые включаются в график работ. Придется нарисовать
схему функциональных модулей, но не следует излишне вдаваться в детали, поскольку
схема вполне может меняться в течение всего процесса работы над проектом. Кроме
того, необходимо решить, какие компоненты должны создаваться в первую очередь,
а какие зависят от других компонентов, после чего построить график разработки ка-
ждого из них.
Даже если все члены группы должны работать над всеми составляющими кода,
обычно имеет смысл возложить персональную ответственность за каждый компонент
на определенное лицо. В конечном итоге, этот человек будет отвечать за неполадки,
связанные с данным компонентом. Кроме того, кто-то должен взять на себя обязан-
ности менеджера сборки (build manager). Менеджер должен обеспечить продвиже-
ние работ над всеми компонентами первой очереди и работать над остальными.
Обычно это же лицо осуществляет управление версиями, речь о котором пойдет ни-
же. Данный сотрудник может также выступать в роли менеджера всего проекта либо
обладать особыми полномочиями.
Использование стандартной структуры каталогов
Перед тем как приступить к работе над проектом, необходимо продумать отраже-
ние структуры компонентов в структуре каталогов Web-сайта. Обычно содержать все
компоненты в одном каталоге так же нецелесообразно, как и помещать все функции в
один гигантский сценарий. Имеет смысл распределить каталоги между компонента-
ми, логикой, содержимым и совместно используемыми библиотеками кода. Структуру
каталогов потребуется документировать и предоставить копию описания каждому
разработчику проекта, в которой он найдет все, что ему нужно.
Документирование и распределение функций
собственной разработки
После написания библиотек функций, их необходимо сделать доступными для дру-
гих программистов команды. Обычно каждый программист создает собственный на-
бор баз данных или функций отладки. Это приводит к непроизводительным затратам
времени. Следует предоставлять доступ к функциям и классам другим членам команды.
Глава 24. Использование РНР и MySQL в крупных проектах
517
Помните, даже если код хранится в общедоступном каталоге, сотрудники не будут
знать об этом до тех пор, пока их специальным образом не уведомить. Создайте сис-
тему документирования внутренних библиотек функций и сделайте ее доступной ос-
тальным программистам.
Управление версиями
Применительно к разработке программных систем, управление версиями рас-
сматривается как искусство управления параллельными изменениями. Обычно сис-
темы управления версиями действуют как центральные хранилища или архивы, при
этом они предоставляют управляемый интерфейс для доступа и коллективного ис-
пользования кода (и, возможно, документации).
Давайте представим ситуацию, когда двум членам группы необходимо трудиться
над одним и тем же файлом. Они могут открыть и редактировать файл одновремен-
но, перезаписывая изменения, внесенные друг другом. Может быть вариант, когда
каждый из них располагает собственной копией файла и автономно редактирует ее
на свой лад. Случается также, что один программист просто бездействует, пребывая в
ожидании, пока другой программист не завершит редактирование файла.
Система управления версиями позволяет решить все обозначенные выше про-
блемы.
Подобные системы способны отслеживать изменения каждого файла в хранилище
таким образом, чтобы пользователь мог видеть не только его текущее состояние, но и
содержимое, связанное с любым моментом времени в прошлом. Эта функция позво-
ляет выполнять откат ошибочного кода, обеспечивая возврат к работоспособной
версии. Определенный набор экземпляров файла можно помечать как окончатель-
ную версию. Это означает, что можно продолжать разработку кода, но всегда иметь
доступ к копии версии, которая на данный момент считается окончательной.
Кроме того, системы управления версиями помогают нескольким программистам
одновременно работать над кодом. Каждый программист может получить копию кода
в хранилище (этот процесс называется выдачей). После внесения изменений новый
вариант можно поместить обратно в хранилище. В этом случае считается, что версия
принята или предоставлена. Поэтому системы управления версиями могут отслежи-
вать, какие пользователи какие изменения вносят.
Обычно подобные системы способны управлять одновременными обновлениями.
Это означает, что два программиста могут одновременно модифицировать один и
тот же файл. Например, Джон и Мэри получили по копии самой последней версии
проекта. Джон завершает модификацию определенного файла и предоставляет его
системе. Мэри также изменяет этот файл и пытается предоставить его. Если внесен-
ные изменения касаются не одной и той же части файла, система выполнит слияние
двух версий. Если изменения конфликтуют между собой, для Мэри выводится соот-
ветствующее уведомление, после чего отображаются две различных версии файла.
Затем она сможет переделать свою версию кода, дабы устранить конфликты.
Система управления версиями, используемая большинством разработчиков для
Unix и/или Open Source, называется CVS (Concurrent Versions System — система парал-
лельных версий). Система CVS относится к категории программного обеспечения с
открытым исходным кодом и входит в состав практически каждой версии Unix.
518
Часть V. Реальные проекты на РНР и MySQL
Кроме того, ее можно приобрести для систем DOS. Windows и Macintosh. Она
поддерживает модель типа клиент-сервер, поэтому код можно просматривать на лю-
бом компьютере через Internet-соединение, если в сети доступен сервер CVS. По этой
причине система использовалась при разработке РНР, Apache, Mozilla и других круп-
ных проектов, по крайней мере, частично.
CVS для своей системы можйо загрузить из Web-сайта по адресу:
ht tp: I /vm. cvshome. org /
Хотя базовая версия CVS является инструментом командной строки, различные
дополнения, включая модули на основе Java и Windows, реализуют для нее более при-
влекательный интерфейс. Дополнения также можно загрузить из упомянутого выше
сайта CVS.
Конкурирующим продуктом подобного рода является Bitkeeper, который исполь-
зуется в нескольких крупных проектах с открытым исходным кодом, среди которых
MySQL и ядро Linux. Для проектов с открытым исходным кодом Bit keeper доступен
бесплатно.
Существуют коммерческие альтернативы. Одна из них, perforce, является доста-
точно функциональной и мощной, выполняется на большинстве стандартных плат-
форм и имеет встроенную поддержку РНР. Несмотря на то что это коммерческий
продукт, доступны бесплатные лицензии для проектов с открытым исходным кодом,
которые можно получить на сайте:
http://www.perforce.сот/
Выбор среды разработки
Обсуждение систем управления версиями переходит в более широкую область,
связанную со средой разработки. Для программирования абсолютно необходимы
лишь текстовый редактор и браузер для целей тестирования кода. Однако гораздо
большей эффективности можно достигнуть в интегрированной среде разработки
(Integrated Development Environment — IDE).
Существует несколько интересных бесплатных проектов по созданию независи-
мой автономной интегрированной среды разработки для РНР, среди которых стоит
отметить KPHPDevelop, которая ориентируется на среду рабочего стола KDE для
Linux. Дополнительную информацию по этому проекту можно получить по адресу:
http://kphpdev.sourceforge.net/
Тем не менее, следует заметить, что в настоящее время лучшие IDE для РНР явля-
ются коммерческими. Мощные и полнофункциональные IDE для РНР предлагают
такие инструментальные средства, как Zend Studio из zend.com, Komodo из actives-
tate, com и PHPEd из nusphere.com. Все перечисленные сайты предлагают свободно
загружаемые пробные версии инструментов, однако для долговременного использо-
вания этих продуктов придется выкладывать определенную сумму денег. Кроме того,
Komodo имеет очень дешевую лицензию для некоммерческого использования инст-
рументального средства.
Глава 24. Использование РНР и MySQL в крупных проектах
519
Документирование проектов
Во время выполнения проекта по созданию программного продукта может разра-
батываться множество различных видов документов, включая (но не ограничиваясь)
следующие:
Проектная документация.
Техническая документация/руководство разработчика.
Словарь данных (включая документацию по классам).
Руководство пользователя (хотя большинство Web-приложений должны быть
самоочевидными).
Здесь наша цель состоит не в том, чтобы обучить написанию документации, а в
том, чтобы предоставить ряд рекомендаций по снижению трудозатрат за счет час-
тичной автоматизации процесса.
В ряде языков существуют методы автоматической генерации некоторых из пере-
численных выше документов — в частности, технической документации и словарей
данных. Например, программа javadoc генерирует дерево HTML-файлов, которое
содержит прототипы и описания членов классов для программ на языке Java.
Для РНР существует немало утилит подобного рода. Ниже перечислены некото-
рые из них:
phpdoc, доступная по адресу:
http://www.phpdoc.de/
Это система, которая использовалась для документирования кода библиотеки
PEAR. Обратите внимание, что термин phpDoc используется для описания це-
лого множества проектов подобного типа, и это — лишь один из них.
PHPDocumentator, доступная по адресу:
http://phpdocu.sourceforge.net/
PHPDocumentator дает возможность получать документы, во многом подобные
генерируемым программой javadoc, и довольно надежна в работе. Кроме того,
создается впечатление, что данный проект разрабатывает наиболее активная
команда из числа перечисленных в этом списке.
phpautodoc, доступная по адресу:
http://sourceforge.net/proj ec ts/phpautodocI
Эта программа также генерирует вывод, подобный утилите javadoc.
Для поиска других приложений подобного рода (и PHP-компонентов вообще) хо-
рошо подходит сайт SourceForge:
http://sourceforge.net
Код, предлагаемый сайтом SourceForge, в основном, используется сообществом
пользователей и разработчиков под Unix/Linux, тем не менее, на нем доступно мно-
жество проектов, ориентированных и на другие платформы.
520
Часть V. Реальные проекты на РНР и MySQL
Создание прототипов
Созданием прототипов (prototyping) называют определенный этап разработки, ши-
роко используемый при написании Web-приложений. Прототип служит удобным
средством отработки требований заказчика. Обычно прототип представляет собой
частично работающую версию приложения, которую можно обсуждать с клиентом и
которая служит основой окончательной версии. Зачастую окончательная версия по-
лучается в результате многочисленных переделок прототипа. Преимущество такого
подхода состоит в возможности тесного взаимодействия с клиентом или конечными
пользователями с целью создания приемлемой системы. Кроме того, в какой-то мере
клиент становится совладельцем продукта.
Чтобы быстро “сколотить” прототип, необходимо обладать определенными на-
выками и располагать некоторыми инструментальными средствами. Именно здесь и
оправдывает себя модульный принцип проектирования. Наличие доступа к набору
готовых компонентов существенно ускоряет построение прототипа. Другим удобным
инструментом быстрого создания прототипов служат шаблоны, которые рассматри-
ваются в следующем разделе.
Построение моделей связано с двумя основными проблемами. Необходимо иметь
о них представление, чтобы избежать затруднений, а также использовать этот подход
с максимальной эффективностью.
Первая проблема связана с тем, что программисты часто находят затруднитель-
ным отбрасывать код, который они по той или иной причине написали. Прототипы
обычно создаются быстро, но впоследствии становится очевидным, что прототип
построен не самым оптимальным образом. Нелепые фрагменты кода еще можно ис-
править, но когда неприемлемой оказывается структура в целом, положение стано-
вится серьезным. Дело в том, что Web-приложения обычно создаются в предельно
сжатые сроки, и времени на исправление может попросту не хватить. В результате
получается неудачно построенная система, которой к тому же трудно управлять.
Во избежание проблем подобного рода, необходимо внедрять элементы планиро-
вания, о чем речь шла выше. Иногда проще что-то переделать заново, нежели пы-
таться исправить существующее. Может показаться, что на планирование просто нет
времени, однако впоследствии оно избавит от множества забот.
Вторая проблема состоит в том, что система может превратиться в вечный прото-
тип. Каждый раз когда, казалось бы, работа завершена, заказчик предлагает очеред-
ные новые усовершенствования, дополнительные функциональные возможности и
обновления внешнего вида сайта. Из-за такого наплыва требований проект, возмож-
но, не сможет быть завершен никогда.
Во избежание подобной ситуации, составьте план с фиксированным количеством
версий и датой, по истечении которой нельзя добавлять новые функции без повтор-
ного планирования, составления новой сметы и графика работ.
Разделение логики и содержимого
Возможно, вам уже знакома идея использования HTML для описания структуры
Web-документов и применение каскадных таблиц стилей (cascading style sheets — CSS)
для описания их оформления. Подобный принцип отделения содержимого от пред-
Глава 24. Использование РНР и MySQL в крупных проектах
521
ставления можно распространить и на создание сценариев. Обычно долговременное
содержание сайтов осуществляется проще, когда реализовано разграничение логики
и содержимого. Это сводится к разделению PHP-кода и HTML-кода.
Для простых проектов с небольшим количеством строк кода эффект от реализа-
ции данного принципа может не оправдать затраченных на него усилий. По мере
разрастания проекта становится важным найти способ разграничения логики и со-
держимого. В противном случае кодом будет все сложнее и сложнее управлять. Если
возникает необходимость изменить оформление Web-сайта, а элементы форматиро-
вания тесно связаны с HTML-кодом, подобного рода работа может превратиться в
настоящий кошмар.
Рассмотрим три общепринятых базовых подхода к разделению логики и содер-
жимого:
Для хранения различных частей содержимого используйте подключаемые
файлы. Это упрощенный подход, однако он вполне эффективен, если сайт в
основном статичный. Данное решение обсуждалось в примере для вымышлен-
ной компании TLA Consulting в главе 5.
Воспользуйтесь функциями либо классами API-интерфейса с набором собст-
венных функций для подключения динамического содержимого к статическим
шаблонам страницы. Такой подход рассматривался в главе 6.
Воспользуйтесь системой шаблонов. Такая система анализирует статические
шаблоны и применяет регулярные выражения для замены дескрипторов-
заполнителей динамическими данными. Главное преимущество данного реше-
ния состоит в том, что проектировать шаблоны может кто-то другой, кому со-
вершенно не требуется вникать в PHP-код. У вас появляется возможность ис-
пользовать предоставляемые шаблоны с минимальными изменениями.
Существует целый набор систем шаблонов. Возможно, наиболее популярной из
них является система Smarty, которая доступна по следующему' адресу:
ht tp://smarty.php.net/
Оптимизация кода
Для тех, чей опыт программирования не связан с Web, оптимизация трактуется
как нечто, имеющее очень большое значение. В случае использования языка РНР
большая часть времени ожидания пользователя Web-приложения связана с подклю-
чением и загрузкой из сети. В это время эффект от оптимизации кода оказывается
весьма несущественным.
Использование простой оптимизации
Тем не менее, существует несколько простых методов оптимизации, которые соз-
дают ощутимый эффект. Многие из них связаны с приложениями, которые через
PHP-код взаимодействуют с базами данных, в том числе и MvSQL. Ниже перечислены
некоторые из методов такого рода:
Уменьшение количества подключений к базам данных. Подключение к базе
данных часто является наиболее медленно выполняющейся частью любого
522
Часть V. Реальные проекты на РНР и MySQL
сценария. Выйти из положения можно за счет организации постоянных со-
единений.
Ускорение запросов к базам данных. Необходимо уменьшать количество за-
просов и проводить их оптимизацию. Для сложных (а, следовательно, медлен-
ных) запросов обычно существует несколько методов оптимизации. Выпол-
няйте запросы через интерфейс командной строки базы данных и тестируйте
различные варианты их ускорения. В MySQL для выявления ошибочных за-
просов можно задействовать оператор EXPLAIN. (Использование этого опе-
ратора рассматривалось в главе 12.) Обычно идея заключается в минимиза-
ции количества соединений и максимальному использованию индексации.
Сведение к минимуму генерации статического содержимого из РНР-кода.
Если каждый фрагмент HTML-кода генерируется операторами echo или
print (), приложение работает намного медленнее. (Это один из аргументов в
пользу разделения логики и содержимого.) То же относится и к динамической
генерации графических кнопок. Лучше сгенерировать все кнопки средствами
РНР один раз, а затем повторно использовать их по мере необходимости. Если
выполняется генерация статического содержимого из функций или шаблонов
при каждой загрузке страницы, имеет смысл реализовать однократный вызов
функций либо использование шаблонов и сохранение результатов.
Использование функций обработки строк вместо регулярных выражений
при малейшей возможности. Для этих функций характерно более высокое
быстродействие.
Использование, где только возможно, строк с одинарными кавычками вме-
сто строк с двойными кавычками. РНР выполняет анализ строк с двойными
кавычками, выискивая переменные, имена которых необходимо заменять их
значениями. Строки с одинарными кавычками не анализируются. С другой
стороны, одинарные кавычки чаще применяют для статического содержимого.
Попытайтесь найти способ избавиться от строк вообще, заменив их статиче-
ским HTML-содержимым.
Использование продуктов Zend
Компания Zend Technologies разрабатывала сценарный механизм РНР (с откры-
тым исходным кодом), начиная с версии 4. В дополнение к базовому механизму мож-
но загрузить утилиту оптимизации Zend Optimizer. Этот многопроходный оптимиза-
тор выполняет оптимизацию кода и может повысить быстродействие сценариев от
40% до 100%. Для выполнения утилиты оптимизации необходима версия РНР 4.0.2
или выше. Исходный код программы не предоставляется, тем не менее, саму про-
грамму можно бесплатно загрузить из Web-сайта Zend по адресу:
http://www.zend.сот/
Этот подключаемый модуль выполняет оптимизацию кода, получаемого в резуль-
тате динамической компиляции разработанного сценария. Среди других программ-
ных продуктов компании Zend Technologies стоит упомянуть Zend Studio, Zend Accel-
erator и Zend Encoder. На указанном выше сайте также доступны и соответствующие
соглашения на коммерческую поддержку продуктов компании.
Глава 24. Использование РНР и MySQL в крупных проектах
523
Тестирование
Пересмотр и тестирование кода является еще одним важным этапом разработки
программного обеспечения, который разработчики зачастую упускают, когда зани-
маются программированием для Web. Очень легко запустить систему для двух-трех
случаев использования, а затем отметить, что она работает нормально. Пренебреже-
ние этим представляет собой довольно-таки распространенную ошибку. Прежде чем
выпускать продукт, необходимо тщательно проанализировать, пересмотреть код и
испытать его на нескольких тестовых сценариях.
Мы рекомендуем два метода снижения количества ошибок в коде. (Полностью из-
бавиться от ошибок не удается никогда и ни при каких условиях, тем не менее, боль-
шинства ошибок вполне можно избежать.)
Во-первых, практикуйте пересмотр кода, когда его должен просмотреть другой
программист и, возможно, предложить некоторые усовершенствования. Подобный
анализ часто выявляет следующие вещи:
Ошибки, пропущенные разработчиком.
Тестовые случаи, которые не были учтены.
Возможности оптимизации.
Возможности совершенствования степени защищенности.
Существующие компоненты, которые можно использовать для усовершен-
ствования фрагментов кода.
Дополнительные функциональные возможности.
Даже если вы разрабатываете код в одиночку, имеет смысл найти коллегу, нахо-
дящегося в подобной ситуации, и анализировать код друг у друга.
Еще один метод предусматривает поиск лиц, которые смогли бы заняться тести-
рованием Web-приложения, ставя себя на место конечных пользователей продукта.
Главное отличие Web-приложений от традиционных настольных систем состоит в
том, что с Web-приложениями работает самая что ни на есть широкая публика. Здесь
не следует рассчитывать, что пользователи обязаны разбираться в компьютерной
технике. Их нельзя снабдить ни длинным руководством, ни даже кратким справочни-
ком. Вместо этого Web-приложения должны быть самодокументируемыми и самооче-
видными. Необходимо учесть все возможные способы работы с приложением со сто-
роны потенциальных пользователей. Разумеется, абсолютным приоритетом обладает
удобство работы с приложением.
Опытному программисту или пользователю Web-среды иногда трудно понять про-
блемы неискушенных пользователей. Одно из решений — найти специалистов по тес-
тированию, которые смогли бы представить типовых пользователей.
В прошлом применялся подход, который предполагал первоначальный выпуск так
называемых бета-версий Web-приложения. Когда, как предполагалось, большинство
ошибок было исправлено, приложение публиковалось для небольшой группы поль-
зователей и невысокой интенсивности трафика сайта. Предложите первой сотне
пользователей бесплатно какие-нибудь полезные услуги в обмен за отзывы о сайте.
Мы гарантируем, что они укажут на такие комбинации данных или случаи использо-
вания, о которых вы, как разработчик, даже и не подозревали. Если создание Web-
524
Часть V. Реальные проекты на РНР и MySQL
сайта заказывает некоторая компания, она обычно может предоставить для его тес-
тирования достаточно неопытных пользователей в лице своих сотрудников. (Суще-
ственное преимущество такого подхода состоит в укреплении чувства причастности к
разработке сайта со стороны клиента.)
Дополнительные источники информации
Область знаний, которой мы коснулись в данной главе, относится к искусству раз-
работки программного обеспечения, поэтому ей посвящено очень много книг. Это
исключительно обширная тема.
Противопоставлению Web-сайта как документа и Web-сайта как приложения по-
священа отличная книга Томаса Пауэла (Thomas A. Powell) под названием Web Site
Engineering: Beyond Web Page Design. В принципе, полезной может оказаться также и лю-
бая книга по разработке программного обеспечения.
Дополнительную информацию по управлению версиями можно найти на Web-
сайте CVS, который находится по адресу:
http://www.cvshome.org/
Самой проблеме управления версиями посвящено не так уж много книг (и это не-
смотря на важность темы!), тем не менее, можно остановиться на книге Карла Фран-
ца Фогеля (Karl Franz Fogel) под названием Open Source Development with CVS, либо же на
книге Г регора Пурди (Gregor N. Purdy), озаглавленной как CVS Pocket Reference.
Широчайший выбор PHP-компонент, интегрированных сред разработки и систем
документирования доступен на сайте SourceForge, который расположен по адресу:
http://sourceforge.net
Многие темы этой главы обсуждаются в статьях на Web-сайте компании Zend. Из
этих статей можно почерпнуть полезную дополнительную информацию. Заодно име-
ет смысл загрузить утилиту оптимизации. Сайт компании Zend находится по адресу:
http://www.zend.com
Если материал, представленный в этой главе, вас заинтересовал, рекомендуем оз-
накомиться с методологией разработки программного обеспечения под названием
“Экстремальное программирование” (“Extreme Programming”), точнее, с той ее ча-
стью, которая посвящена областям, где требования часто меняются, коими являются
и Web-приложения. Вот адрес Web-сайта, на котором можно найти исчерпывающую
информацию по проблемам экстремального программирования:
http://www.extremeprogramming.org
Что дальше
В главе 25 мы рассмотрим различные виды ошибок программирования, ознако-
мимся с типовыми сообщениями об ошибках РНР, а также исследуем технологию об-
наружения ошибок.
Глава 24. Использование РНР и MySQL в крупных проектах
525
25
Отладка
Зта глава полностью посвящена вопросам отладки PHP-сценариев. Если вы ра-
зобрали некоторые примеры книги либо ранее работали с РНР, то наверняка
уже выработали собственные навыки и технологии отладки. С возрастанием сложно-
сти проектов отладка становится все более и более затруднительной. Несмотря на
рост вашего профессионального уровня как разработчика, ошибки могут затрагивать
множество файлов либо появляться в результате взаимодействия кода, написанного
разными программистами.
В главе, помимо прочих, рассматриваются следующие темы:
Типы программных ошибок:
• Синтаксические ошибки.
• Ошибки времени выполнения.
• Логические ошибки.
Сообщения об ошибках.
Уровни выдачи сообщений об ошибках.
Генерация собственных ошибок.
Изящная обработка ошибок.
Программные ошибки
Вне зависимости от используемого языка программирования существуют три ос-
новных типа программных ошибок:
Синтаксические ошибки.
Ошибки времени выполнения.
Логические ошибки.
Ниже представлен краткий обзор каждого типа, после чего последует обсуждение
возможной тактики обнаружения, обработки, предупреждения и устранения ошибок.
Синтаксические ошибки
Языки в общем случае характеризуются набором правил, называемых синтаксисом
(syntax), который касается правильного использования всех операторов. Это отно-
сится как к естественным языкам, например, русском}’, английском}' и прочим, так и
языкам программирования вроде РНР. Если утверждение не соответствует правилам
языка, говорят, что оно содержит синтаксическую ошибку. Синтаксические ошибки
часто называют ошибками разбора в случае интерпретируемых языков, таких как
РНР, либо ошибками компиляции, когда речь идет о компилируемых языках наподо-
бие С и Java.
Если мы нарушим правила синтаксиса, скажем, английского языка, скорее всего,
люди нас поймут. С языками программирования подобная ситуация случается редко.
Когда в сценарии нарушается синтаксис РНР, программа синтаксического анализа
не сможет обработать сценарий, частично или полностью. Живые люди достаточно
хорошо умеют извлекать информацию из неполных или противоречивых данных.
В то же время, компьютеры способностью подобного рода не обладают.
Помимо прочего, синтаксис РНР требует, чтобы все операторы завершались точ-
кой с запятой, строки заключались в кавычки, а передаваемые функциям параметры
отделялись друг от друга запятыми и помещались в скобки. Если нарушить эти прави-
ла, сценарий окажется неработоспособным, а при первой же попытке его выполне-
ния сгенериру ется соответствующее сообщение об ошибке.
Одной из сильных сторон РНР является выдача информативных сообщений об
ошибках. Обычно упомянутые сообщения указывают на характер неполадки, какой
файл содержит ошибку' и в какой строке она обнаружена.
Вот только один пример сообщения об ошибке:
Parse error: parse error, unexpected 11 7 in
/home''book/public_html/chapter25/error.php on line 2
(Ошибка синтаксического анализа: ошибка разбора, недопустимая 7 7 7 в файле
'home/book/public_html/chapter25/error.php в строке 2)
К этой ошибке привел следующий сценарий:
<?
$date = date(m.d.y7);
Здесь предпринималась попытка передать в функцию date () строку, но по ошиб-
ке была пропущена открывающая кавычка, отмечающая начало строки.
Простые синтаксические ошибки, подобные данной, обычно обнаруживаются до-
вольно-таки легко. Можно допустить аналогичную, однако более сложную в обнаружении
ошибку, если не завершить строку7 кавычкой, как показано в следующем примере:
<7
$date = date(’m.d.y);
?>
Этот сценарий приводит к возникновению следующей синтаксической ошибки:
Parse error: Parse error, unexpected $end in
/home/book/public_html/chapter25/error.php on line 4
(Ошибка синтаксического анализа: ошибка разбора, недопустимый Send в файле
/home/book/public_html/chapter25/error.php в строке 4)
Очевидно, что ошибка не может содержаться в четвертой строке, поскольку сце-
нарий состоит всего лишь из трех строк. Подобным образом генерируются сообще-
Глава 25. Отладка
527
ния об ошибках, когда пропущены закрывающие одинарные или двойные кавычки, а
также скобки любого вида.
Аналогичную синтаксическую ошибку генерирует и сценарий, показанный ниже:
<?
if (true)
{
echo 'здесь допущена ошибка';
Обнаружение подобных ошибок может быть затруднено, если они появляются в
результате комбинирования нескольких файлов. То же относится к случаю, когда
ошибка содержится в файле большого размера. Сообщение об ошибке "parse error
on line 1001" (“синтаксическая ошибка в строке 1001”) файла, который содержит
1000 строк, может создать большие затруднения, однако по ходу дела и натолкнет на
мысль писать модульный код.
По общепринятому мнению, синтаксические ошибки наиболее просты в обнару-
жении. Сообщения РНР достаточно точно указывают, где их следует искать.
Ошибки времени выполнения
Выявлять и исправлять ошибки времени выполнения обычно сложнее. Синтакси-
ческие ошибки явно содержатся в сценарии и обнаруживаются программой анализа,
когда этот сценарий начинает выполняться. Ошибки времени выполнения не вызва-
ны исключительно содержимым сценария. Они могут зависеть от взаимодействия
сценариев с другими событиями или условиями.
Например, следующий оператор:
require (1 filename.php1);
вполне допустим. Он не содержит синтаксических ошибок.
Тем не менее, приведенный выше оператор может привести к ошибке времени
выполнения. Если его выполнить, когда файл filename.php не существует, либо за-
пускающий сценарий пользователь не имеет прав чтения этого файла, будет получе-
но приблизительно такое сообщение об ошибке:
Fatal error: main() [function.require]: Failed opening
required 'filename.php1
(include_path=1.:/usr/local/lib/php') in
/home/book/public_html/chapter25/error.php on line 1
(Неисправимая ошибка: main() [function.require]: ошибка открытия требуемого
файла 'f i1ename.php'
(include_path=1.:/usr/local/lib/php') в файле
/home/book/public_html/chapter25/error.php в строке 1)
Код написан вполне корректно, но указываемый в нем файл в момент выполнения
сценария может либо существовать, либо отсутствовать. В этой связи возможно воз-
никновение ошибки времени выполнения.
Следующие три строки, по отдельности, являются допустимыми РНР-операто-
рами. К сожалению, в совокупности они приводят к попытке выполнить невозмож-
ное действие — деление на ноль.
528
Часть V. Реальные проекты на РНР и MySQL
$i = Ю;
$j = 0;
$k = $i/$k;
Показанный выше фрагмент кода генерирует следующее предупреждение:
Warning: Division by zero in
/home/book/public_html/chapter25/div0.php on line 3
(Предупреждение: Деление на ноль в файле
/horne/book/public_htrnl/chapter25/div0 .php в строке 3)
Это сообщение существенно упрощает исправление ошибки. Не очень многие бу-
дут намеренно задавать в коде деление на ноль, однако отсутствие проверки пользо-
вательского ввода часто приводит к ошибкам подобного типа.
Это один из многочисленных примеров ошибок времени выполнения, которые
могут возникать в процессе тестирования кода.
Представленный далее код иногда приводит к возникновению той же самой
ошибки, однако ее гораздо труднее изолировать и исправить, поскольку она возника-
ет нерегулярно:
$i = 10;
$k = $i/$_REQUEST['input'];
Ниже перечислены распространенные причины ошибок времени выполнения:
Вызов несуществующих функций.
Чтение и запись в файлы.
Взаимодействие с MySQL и другими базами данных.
Подключение к сетевым службам.
Отсутствие проверки данных, вводимых пользователем.
Далее будут кратко рассмотрены все перечисленные причины.
Вызов несуществующих функций
Ошибку такого рода легко допустить случайно. Имена встроенных функций часто
бывают неоднородными. Почему в имени strip_tags () присутствует символ подчер-
кивания, а в имени stripslashes () его нет?
Кроме того, возможен вызов ваших собственных функций, которые не содержат-
ся в текущем сценарии, но могут существовать в другом месте. Если код содержит вы-
зов несуществующей функции, например:
nonexistent-function();
или
mispeled_function();
будет получено следующее сообщение об ошибке:
Fatal error: Call to undefined function: nonexistent-function()
in /home/book/public_html/chapter25/error.php on line 1
(Неисправимая ошибка: Обращение к несуществующей функции: nonexistent-function()
в файле /home/book/public_html/chapter25/error.php в строке 1)
Глава 25. Отладка
529
Точно так же, если вызвать существующую функцию, но с неверным числом пара-
метров, будет выведено соответствующее предупреждение.
Функция strstr () требует передачи ей двух строк — в одной из них выполняется
поиск, а другая служит искомым фрагментом. Если вызвать функцию следующим об-
разом:
strstr();
будет выведено следующее предупреждение:
Warning: Wrong parameter count for strstr() in
/home/book/public_html/chapter25/error.php on line 1
(Предупреждение: неверное число параметров для strstr() в файле
/home/book/public_html/chapter25/error.php в строке 1)
Следующий сценарий приводит к той же ошибке:
<?
if($var == 4)
{
strstr();
}
7>
Однако вызов функции strstr () не осуществляется за исключением тех случаев,
когда переменная $var принимает значение 4. При этом предупреждение также не
выводится. Интерпретатор РНР не тратит время на синтаксический анализ разделов
кода, которые в данный момент не выполняются, так что будьте внимательны при
тестировании, чтобы охватить каждый фрагмент кода!
Неправильный вызов функции легко допустить, но сообщение об ошибке точно
идентифицирует строку и имя функции, что делает исправление ошибки довольно-
таки простым. Обнаружение подобных ошибок затруднено лишь в случае, когда про-
цесс тестирования несовершенен и не проверяется весь условно выполняемый код.
Одна из задач тестирования состоит в выполнении каждой строки кода. Вторая зада-
ча заключается в проверке всех граничных условий и классов ввода.
Чтение и запись в файлы
В процессе использования программы могут возникать любые ошибки, но одни из
них случаются чаще других. Ошибки доступа к файлам достаточно вероятны, чтобы
заранее предусмотреть методы их эффективной обработки. Жесткие диски могут
давать сбои либо переполняться, а ошибки со стороны пользователей приводят к из-
менению прав доступа к файлам и каталогам.
Обычно в часто приводящих к ошибкам функциях, таких как fopen (), предусмат-
ривается возвращаемое значение, идентифицирующее ошибку. Для функции fopen ()
таким значением является false.
Для подобных функций необходимо тщательно проверять возвращаемое значение
при каждом вызове и обрабатывать ошибки.
530
Часть V. Реальные проекты на РНР и MySQL
Взаимодействие с MySQL и другими базами данных
Подключение к базе данных MySQL и ее использование может привести к генера-
ции множества ошибок. Одна только функция mysqli—connect () может приводить к
следующим ошибкам:
•Warning: mysqli_connect() [function.mysqli-connect]: Can't connect to
MySQL server on 'localhost' (10061)
Предупреждение: mysqli_connect() [function.mysqli-connect]: Невозможно
подключиться к серверу MySQL на 'localhost' (10061)
•Warning: mysqli_connect() [function.mysqli-connect]: Unknown MySQL Server
Host ‘hostname’ (11001)
Предупреждение: mysqli_connect() [function.mysqli-connect]: Неизвестный
хост сервера MySQL 'имя_хоста' (11001)
•Warning: mysqli_connect() [function.mysqli-connect]: Access denied for
user: ‘username’STocalhost’ (Using password: YES)
Предупреждение: mysqli—connect() [function.mysqli-connect]: Доступ запре-
щен для пользователя: 'имя_пользователя@1оса11гс^' (Использование пароля:
ДА)
Несложно догадаться, что функция mysqli_connect () в случае ошибки возвраща-
ет значение false. Это означает, что данные типы распространенных ошибок доста-
точно легко отслеживать и обрабатывать.
Если не остановить нормальное выполнение сценария и не предусмотреть обра-
ботку возникшей ошибки, сценарий попытается продолжить взаимодействовать с
базой данных. Попытка выполнения запросов к базе данных и получения результатов
без нормального соединения с MySQL заставит посетителей наблюдать экран весьма
непрофессионального вида, притом полный сообщений об ошибках.
Множество других часто используемых PHP-функций, такие как mysqli_query (),
которые связаны с MySQL, также возвращает значение false в случае, когда что-то
идет не так, как запланировано.
Если ошибка все же возникает, для получения текста сообщения с описанием
ошибки можно воспользоваться функцией mysqli_error(), а для вывода кода ошиб-
ки — функцией mysql i_errno (). Если последняя вызванная функция MySQL не сгене-
рировала ошибки, mysqli_error () возвращает пустую строку, a mysqli_errno () —
значение 0.
Предположим, что выполнено подключение к серверу и выбрана база данных для
использования. Тогда показанный ниже фрагмент кода:
Sresult = mysqli_query($db, 'select * from does_not_exist');
echo mysqli_errno($db);
echo '<BR>';
echo mysqli_error($db);
может привести к генерации следующего сообщения:
1146
Table 'dbname.does_not_exist' doesn't exist
(1146
Таблица 'dbname.does_not_exist' не существует)
Глава 25. Отладка
531
Обратите внимание, что вывод указанных функций относится к выполнению по-
следней вызванной функции MySQL (кроме mysqli_error () и mysqli_errno ()). Если
необходимо знать результаты выполнения команды, следует обязательно выполнить
проверку до обращения к какой-либо другой функции.
Подобно сбоям доступа к файлам, возникают и сбои во время взаимодействия с
базами данных. Даже после тщательной разработки и тестирования некоторой служ-
бы случается, что демон MySQL (mysqld) выдает сбой либо не остается свободных
соединений. Если база данных хранится на другом компьютере, ее работа зависит от
другого набора аппаратных и программных компонентов, которые могут сбоить. Это
относится к сетевым соединениям, сетевым адаптерам, маршрутизаторам и другим
средствам связи между Web-сервером и компьютером с базой данных.
Прежде чем использовать результаты, необходимо обязательно проверять успеш-
ность запросов к базе данных. Нет смысла пытаться выполнить запрос после сбоя
соединения с базой данных, а также извлекать и обрабатывать результаты запроса,
выполнение которого завершилось неудачей.
Здесь следует отметить различие между сбоем запроса и случаем, когда запрос
просто не возвращает данные либо не изменяет какие-то строки таблицы.
SQL-запрос, который содержит синтаксические ошибки языка SQL либо относит-
ся к несуществующим базам данных, таблицам или столбцам, может привести к сбою.
Например, приведенный ниже запрос:
select * from does_not_exist;
генерирует ошибку, поскольку таблица с именем does_not_exist не существует. Со-
общение об ошибке и ее номер можно получить с помощью функций mysql i_errno ()
nmysqli_error ().
Синтаксически правильный SQL-запрос, который также адресуется только к суще-
ствующим базам данных, таблицам и столбцам, обычно не генерирует ошибку. Тем не
менее, если запрашивается пустая таблица, либо ведется поиск несуществующих дан-
ных, возврата результатов может и не быть. Предположим, что выполнено успешное
соединение с базой данных, и существует таблица с именем tl, а в ней — столбец с
именем cl. Тогда следующий запрос:
select * from tl where cl = 'not in database';
окажется успешным, тем не менее, не вернет никаких результатов.
Прежде чем использовать результаты запроса, необходимо выполнить проверку
на возможное присутствие ошибки, а также на предмет отсутствия возвращаемых
данных.
Подключение к сетевым службам
Несмотря на то что (теоретически) устройства и программное обеспечение ло-
кальной системы могут давать сбой, это бывает редко, за исключением случаев, когда
их трудно счесть кондиционными. При использовании сети для подключения к дру-
гим компьютерам и программ, выполняемых на них, необходимо учитывать, что оп-
ределенная часть системы будет часто выдавать сбои. Во время соединения между
двумя компьютерами задействуются многочисленные устройства и службы, которые
пользователем не контролируются.
532
Часть V. Реальные проекты на РНР и MySQL
Еще раз стоит подчеркнуть, что необходимо тщательно проверять значения, воз-
вращаемые функциями, которые призваны взаимодействовать с сетевыми службами.
Например, следующий вызов функции:
$sp = fsockopen ( 'localhost', 5000 );
в случае неудачной попытки подключения к порту' 5000 компьютера localhost не
приведет к генерации предупреждающего сообщения.
Если же переписать фрагмент кода следующим образом:
$sp = fsockopen ( 'localhost', 5000, &$errorno, &$errorstr );
if(1$sp)
echo "ОШИБКА: Serrorno: $errorstr“;
будет подавляться выдача встроенного сообщения об ошибке, проверяться возвра-
щаемое значение на предмет возникновения ошибки и, в результате, отображаться
сообщение, которое поможет ее исправить. В этом случае вывод будет таким:
ОШИБКА: 10035: A non-blocking socket operation could not be
completed immediately.
(ОШИБКА: 10035: Неблочная операция с сокетом не может быть завершена немед-
ленно . )
Ошибки времени выполнения гораздо сложнее устранить по сравнению с синтак-
сическими ошибками, поскольку программа синтаксического анализа не может их
выявить при первом выполнении кода. Поскольку ошибки времени выполнения воз-
никают в результате некоторой комбинации событий, могут возникать сложности в
их обнаружении и устранении. Синтаксический анализатор не может автоматически
указать, что определенная строка сгенерирует ошибку. Задача тестирования как раз и
состоит в том, чтобы смоделировать одну из ситуаций, приводящих к возникновению
ошибки.
Обработка ошибок времени выполнения требует в какой-то мере предвидения
возможных ошибочных ситуаций с тем, чтобы предпринять соответствующие дейст-
вия. Кроме того, необходимо выполнять тщательное тестирование с имитацией каж-
дого класса ошибок времени выполнения, которые только могут произойти.
Это не означает, что следует пытаться имитировать все возможные ошибки. На-
пример, MySQL может генерировать около 200 различных ошибок со своими номе-
рами и связанными с ними сообщениями. Следует сымитировать ошибки в каждом
вызове функции, которая может вызвать сбой, а также ошибки каждого типа, кото-
рый обрабатывается отдельным блоком кода.
Отсутствие проверки данных, вводимых пользователем
Мы часто делаем предположения относительно данных, которые будут вводиться
пользователями сайта. Если эти данные не оправдывают наших ожиданий, они могут
привести к ошибкам, причем как времени выполнения, так и логическим.
Классический пример ошибки времени выполнения состоит в обработке вводи-
мых пользователем данных, когда к ним забывают применить функцию добавления
обратных косых addslashes (). В таких случаях, если имя пользователя содержит
апостроф, например, “ОТенри”, функция базы данных сгенерирует ошибку.
Ошибки в результате предположений относительно корректности вводимых
пользователем данных более подробно рассматриваются в следующем разделе.
Глава 25. Отладка
533
Логические ошибки
Логические ошибки могут оказаться наиболее трудными для обнаружения и устра-
нения. К ним относятся случаи, когда вполне допустимый код выполняется как было
запрограммировано, однако автор кода преследовал совершенно другие намерения.
Логические ошибки могут быть вызваны простыми опечатками, как показано в
следующем примере:
for ( $i = 0; $i < 10; $i++ );
{
echo 'Что-то происходит...<BR>';
}
Этот фрагмент кода является вполне допустимым. Здесь совершенно не нарушает-
ся синтаксис РНР. Какие-то внешние службы не задействованы, поэтому ошибки вре-
мени выполнения весьма маловероятны. Вот только код выполняет не те действия,
которые могут показаться заданными на первый взгляд.
Может показаться, что строка "Что-то происходит..." должна выводиться в
цикле for ровно 10 раз. Наличие лишней точки с запятой в конце первой строки оз-
начает, что цикл не распространяется на последующие строки. Цикл for будет вы-
полнен 10 раз безрезультатно, а затем один раз будет выполнен оператор echo.
Поскольку приведенный выше код вполне допустим, хотя и не дает эффекта, син-
таксический анализатор не выведет никаких сообщений. Компьютеры неплохо
справляются с определенными задачами, но они не обладают здравым смыслом или
высоким интеллектом. Машина в точности выполняет то, что ей указывают. В ре-
зультате очень важно добиться того, чтобы инструкции в точности соответствовали
замыслу разработчика.
Логические ошибки не означают невозможность выполнения кода, а вызываются
лишь неудачной попыткой программиста точно выразить с помощью кода свои наме-
рения. Поэтому такие ошибки не могут обнаруживаться автоматически. Ни код
ошибки, ни сообщение о ней не выводятся. Логические ошибки выявляются только в
результате тщательного тестирования.
Ошибку, подобную рассмотренной в предыдущем примере, легко допустить, одна-
ко столь же легко исправить. Все дело в том, что при первом же выполнении кода
вывод будет отличаться от запланированного. Большинство логических ошибок го-
раздо более коварно.
Логические ошибки, вызывающие затруднения, обычно получаются в результате
неверных предположений, проделанных разработчиками. В главе 24 рекомендова-
лось подключить других программистов для проверки кода и подсказки дополни-
тельных тестовых случаев, а также привлечь на тестирование вместо разработчиков
представителей конечных пользователей. Если разработчик будет выполнять тести-
рование самостоятельно, то весьма вероятно, что код останется построенным на ос-
нове его предположений, что пользователи будут вводить только лишь корректные
данные, как того от них ожидают.
Предположим, что коммерческий сайт содержит текстовое поле Order Quantity
(количество заказанного товара). Можно ли предположить, что пользователи будут
вводить только положительные числа? Если посетитель введет -10, будет ли про-
грамма увеличивать остаток на кредитной карточке на сумму десятикратной стоимо-
сти данного товара?
534
Часть V. Реальные проекты на РНР и MySQL
Давайте предположим, что форма содержит поле ввода суммы в долларах. Допус-
кается ли ввод суммы со знаком доллара, либо его указывать нельзя? Разрешено ли
разделять тысячи запятыми? Некоторые проверки подобного рода можно выполнять
на стороне клиента (например, с использованием языка JavaScript), тем самым не-
сколько снизив нагрузку на сервер.
Если информация передается на другую страницу, возможна ли ситуация, когда пере-
даваемая строка содержит недопустимые символы для URL-адреса, например, пробелы?
Количество возможных логических ошибок совершенно не ограничено. Не сущест-
вует автоматизированного метода их выявления. Единственное решение предполагает,
во-первых, избегать в коде сценария явных предположений о (корректном) вводе поль-
зователя и, во-вторых, тщательно проверять все возможные типы допустимого и недо-
пустимого ввода, чтобы в любом случае получался ожидаемый результат.
Вспомогательное средство
отладки переменных
С возрастанием сложности проектов возникает необходимость в том, чтобы рас-
полагать утилитой, которая бы помогала выявлять причины ошибок. В листинге 25.1
представлен фрагмент кода, который вы можете счесть весьма полезным. Этот код
выводит содержимое переменных, передаваемых странице.
Листинг 25.1. dump variables .php — этот код может быть включен в страницы для
вывода содержимого переменных в целях отладки
<?
// Эти строки форматируют выводимую информацию в виде
/7 HTML-комментариев и последовательно вызывают функцию dump_array()
echo "\п<!-- НАЧАЛО ДАМПА ПЕРЕМЕННЫХ —>\п\П";
echo НАЧАЛО ПЕРЕМЕННЫХ GET —>\п”;
echo ' . dump__array ($HTTP_GET_VARS) . ” -->\n” ;
echo «<! — НАЧАЛО ПЕРЕМЕННЫХ POST -->\n";
echo ',dump_array($HTTP_POST_VARS)." —>\n";
echo "<!-- НАЧАЛО ПЕРЕМЕННЫХ СЕАНСА -~>\n";
echo '<! — ',dump_array($HTTP_SESSION_VARS)." -->\n";
echo "<!-- НАЧАЛО ПЕРЕМЕННЫХ COOKIE-НАБОРА —>\n";
echo '<!-- ',dump_array($HTTP_COOKIE_VARS).“ —>\n";
echo "\n<!-- КОНЕЦ ДАМПА ПЕРЕМЕННЫХ —>\n";
// Функция dump_array() использует встроенную функцию print_r()
// и отменяет все завершающие HTML-дескрипторы комментариев
function dump_array($array)
{
$output = print_r($array, true) ;
Soutput = str_replace>', $output);
return $output;
Глава 25. Отладка
535
Показанный в листинге 25.1 код выводит четыре массива переменных, принимае-
мых страницей. Если страница вызывается с GET-переменными, POST-переменными,
cookie-наборами либо переменными сеанса, все они будут выведены.
Выводимые значения помещаются в пару HTML-дескрипторов комментариев,
чтобы они были доступными для просмотра, но не конфликтовали с методами обра-
ботки браузером видимых элементов страницы. Сокрытие отладочной информации в
комментариях, как было сделано в листинге 25.1, позволяет не удалять код отладки
вплоть до последнего момента. Функция dump_array () представляет собой оболочку
для функции print_r (), дополнительно выполняя лишь отмену всех завершающих
HTML-дескрипторов комментариев.
Точный вывод зависит от передаваемых странице переменных. Если фрагмент
кода из листинга 25.1 добавить в листинг 22.4, который содержит один из примеров
аутентификации из главы 22, то сценарий дополнительно сгенерирует следующие
строки:
<!-- НАЧАЛО ДАМПА ПЕРЕМЕННЫХ -->
<!-- НАЧАЛО ПЕРЕМЕННЫХ GET -->
<!-- Array
(
)
<!-- НАЧАЛО ПЕРЕМЕННЫХ POST -->
<!-- Array
(
[userid] => testuser
[password] => password
)
<!-- НАЧАЛО ПЕРЕМЕННЫХ СЕАНСА -->
<I — Array
(
)
<!-- НАЧАЛО ПЕРЕМЕННЫХ COOKIE-НАБОРА -->
<!-- Array
(
[PHPSESSID] => b2b5f56fad986dd73af33f470f3cl865
)
<!-- КОНЕЦ ДАМПА ПЕРЕМЕННЫХ -->
Несложно заметить, что отображаются POST-переменные, отправленные из формы
регистрации, выводимой на предыдущей странице, — userid и password. Как упомина-
лось в главе 22, РНР использует cookie-набор для связывания переменных сеанса с оп-
ределенными пользователями. Сценарий выводит псевдослучайное число PHPSESSID,
хранимое в данном ключе для идентификации определенного пользователя.
536
Часть V. Реальные проекты на РНР и MySQL
Уровни выдачи сообщений об ошибках
РНР позволяет устанавливать степень “тщательности” обработки ошибок. Вы мо-
жете задавать типы событий, для которых будут генерироваться сообщения. По
умолчанию РНР выводит сообщения обо всех ошибках, которые не относятся к кате-
гории уведомлений.
Для установки уровня выдачи сообщений используется набор предопределенных
констант, которые перечислены в табл. 25.1.
Каждая константа представляет тип ошибки, для которой будет генерироваться
сообщение либо же эта ошибка будет игнорироваться. Например, если задать уровень
ошибок E_ERROR, будут выводиться сообщения только о неисправимых ошибках. До-
пускается объединение констант методами двоичной арифметики с целью получения
различных уровней сообщений об ошибках.
Устанавливаемый по умолчанию уровень — все сообщения за исключением уве-
домлений — определяется следующим образом:
E_ALL & ~E_NOTICE
Приведенное выражение включает две предопределенных константы, объеди-
ненных с помощью операций поразрядной арифметики. Амперсанд (&) обозначает
поразрядную операцию “И” (AND), а тильда (~) — поразрядную операцию “НЕ”
(NOT). Выражение можно прочитать так: E_ALL AND NOT E_NOTICE.
Таблица 25.1. Константы, определяющие уровень сообщений об ошибках
Значение Имя Описание
1 EJERROR Сообщения о неисправимых ошибках времени выполнения.
2 E_WARNING Сообщения об исправимых ошибках времени выполнения.
4 E_PARSE Сообщения об ошибках синтаксического анализатора.
8 E_NOTICE Уведомления и предупреждения о том, что выполнен- ные действия могут быть ошибочными.
16 E_CORE_ERROR Сообщения о сбоях запуска механизма РНР .
32 E_CORE_WARNING Сообщения об исправимых ошибках в процессе запуска механизма РНР.
64 E_COMPILE_ERROR Сообщения об ошибках компиляции.
128 E_COMPILE_WARNING Сообщения об исправимых ошибках компиляции.
256 E_USER_ERROR Сообщения об ошибках, сгенерированных пользователем.
512 E_USER_WARNING Сообщения о предупреждениях, сгенерированных поль- зователем.
1024 EUSER_NC'TICE Сообщения об уведомлениях, сгенерированных пользо- вателем.
2047 E_ALL Сообщения обо всех ошибках и предупреждениях.
2048 E_STRICT Сообщения об использовании устаревших и нерекомен- дуемых функциях: не включено в E_ALL, однако исклю- чительно полезно при пересмотре кода.
Глава 25. Отладка
537
Константа E_ALL представляет собой комбинацию всех типов ошибок. Ее можно
заменить, связав все остальные константы поразрядной операцией ИЛИ (OR, |):
E_ERROR | E-WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING j
E__COMPILE_ERROR | E_COMPILE_WARNING | E_USER__ERROR | E_USER_WARNING |
E_USER_NOTICE
Совершенно аналогично реализуется устанавливаемый по умолчанию уровень со-
общений, за исключением того, что отсутствует уровень уведомлений (константа
E_NOTICE):
E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING |
E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING |
E_USER_NOTICE
Изменение настроек уровня сообщения
об ошибках
Настройки уровня сообщений об ошибках могут изменяться глобально в файле
php. ini либо же для каждого сценария в отдельности.
Для того чтобы изменить уровень сообщений для всех сценариев, необходимо мо-
дифицировать следующие четыре строки стандартного файлаphp. ini:
error_reporting = E__ALL & ~E_NOTICE
display_errors = On
log_errors = Off
track_errors = Off
Стандартные глобальные настройки задают:
вывод всех сообщений, кроме уведомлений;
направление сообщений об ошибках в виде HTML-кода на стандартное устрой-
ство вывода;
отсутствие протоколирования сообщений надиске:
отсутствие отслеживания ошибок, сохранение сообщений в переменной
$Php_errormsg.
Чаще всего уровень выдачи сообщений изменяют таким образом, чтобы он соот-
ветствовал комбинации E_ALL | E_STRICT. В результате будет выводиться огромное
число уведомлений. Они могут указывать не только на ошибки, но и на неэффектив-
ное использование возможностей РНР, а также на свойства языка автоматически
присваивать переменным значение 0 в процессе инициализации.
Иногда во время отладки имеет смысл установить более высокий уровень сообще-
ний error_reporting. Если разработчик самостоятельно подготавливает информа-
тивные сообщения об ошибках, в окончательном варианте кода имеет смысл отклю-
чить опцию отображения сообщений на экране display_errors и задействовать
опцию протоколирования ошибок log_errors. При этом уровень выдачи сообщений
остается высоким. В случае сбоев можно будет просмотреть подробную информацию
в журнальных файлах. Вместе с тем поведение программы будет восприниматься как
высокопрофессиональное.
538
Часть V. Реальные проекты на РНР и MySQL
Включение опции отслеживания ошибок track_errors помогает выявлять ошиб-
ки в собственном коде вместо того, чтобы позволить среде РНР предоставлять стан-
дартные функциональные возможности, предназначенные для этого. Хотя РНР вы-
водит довольно-таки информативные сообщения об ошибках, когда возникают сбои,
стандартное поведение среды выглядит не особенно привлекательно.
Как только случается неисправимая ошибка, РНР по умолчанию выводит следующее:
<br>
<Ь>Тип ошибки</Ь>: сообщение об ошибке в файле <Ь>путь/имя.рЬр</Ь>
в строке <Ь>номер строки</ЬхЬг>
и прекращает выполнение сценария. В случае исправимой ошибки выводится тот же
текст, однако выполнение сценария может продолжаться.
Выводимый HTML-код описывает ошибку, тем не менее, выглядит достаточно не-
профессионально. Стиль сообщения об ошибке вряд ли будет соответствовать общей
концепции оформления сайта. Кроме того, если содержимое страницы отображается в
таблице, некоторые пользователи могут вообще не увидеть вывода, если их браузеры
бестолково обрабатывают стандартный HTML. Все дело в том, что HTML-код, который
открывает, но не закрывает элементы таблицы, например:
<table>
<tr><td>
<br>
<Ь>Тип ошибки</Ь>: сообщение об ошибке в файле <Ь>путь/имя,php</b>
в строке <Ь>номер строки</bxbr>
дает в некоторых браузерах пустой экран.
Совершенно не обязательно сохранять стандартный режим обработки ошибок
РНР либо даже использовать одинаковые настройки для всех файлов. Для измене-
ния уровня сообщений об ошибках в текущем сценарии можно вызвать функцию
error_reporting().
Передача в эту функцию константы либо комбинации констант устанавливает
уровень точно так же, как и аналогичная директива файла php. ini. Функция возвра-
щает предыдущий уровень выдачи сообщений. Ниже приведен достаточно распро-
страненный метод использования упомянутой функции:
// Отключить сообщения об ошибках
$old_level = error_reporting(0);
// Здесь помещается код, который генерирует предупреждения.
,'/ Повторно включить режим выдачи сообщений об ошибках
error_reporting($old_level);
В этом фрагменте кода отключается режим вывода сообщений об ошибках, что
позволяет выполнять код, способный генерировать предупреждения, который ото-
бражать нежелательно.
Не стоит отключать сообщения об ошибках и предупреждениях навсегда, по-
скольку' это серьезно затруднит поиск и исправление ошибок.
Глава 25. Отладка
539
Генерация собственных ошибок
Для генерации собственных ошибок применяется функция trigger_error (). Сге-
нерированные подобным образом ошибки будут обрабатываться так же, как и обыч-
ные ошибки РНР.
Функции потребуется передать сообщение об ошибке, а также, необязательно, тип
ошибки. Допустимыми являются следующие типы: E_USER_ERROR, E_USER_WARN ING
либо E_USER_NOTICE. Если тип не указан, по умолчанию принимается значение
E_USER_NOTICE.
Ниже показан пример применения функции trigger_error ():
trigger_error("Этот компьютер самоуничтожится через 15 секунд", E_USER_WARNING);
Изящная обработка ошибок
Если у вас есть опыт программирования на языках C++ и Java, то вы должны хоро-
шо знать механизм исключений. Исключения позволяют функциям предупреждать об
ошибках и задействовать соответствующие обработчики исключений. Исключения
появились только в версии РНР5, и они являются прекрасным способом обработки
ошибок, особенно в крупных проектах. Исчерпывающее описание исключений было
представлено в главе 7.
Если разрабатываемый вами код должен функционировать в среде РНР4, можете
сымитировать похожее поведение с помощью ошибок, генерируемых пользователем,
и пользовательских обработчиков ошибок; тем не менее, сейчас, когда РНР поддер-
живает исключения, упомянутый подход становится менее значимым. Как упомина-
лось ранее, можно генерировать свои собственные ошибки, а также поддерживать
собственные обработчики ошибок.
Функция set_error_handler () дает возможность предоставить функцию, которая
вызывается, когда происходят пользовательские ошибки, предупреждения и уведом-
ления. При вызове set_error_handler () указывается имя функции, которая будет в
дальнейшем служить в качестве обработчика ошибок.
Функция обработки ошибок должна принимать два параметра — тип ошибки и со-
общение. В зависимости от этих двух переменных, функция может выбирать способ
обработки ошибки. Тип ошибки должен соответствовать, одной из предопределен-
ных констант. Сообщение представляет собой описательную строк}-.
Ниже представлен пример вызова функции set_error_handler ():
set_error_handler("my_error_handler');
После указания среде РНР на необходимость использования функции
my_error_handler () потребуется подготовить функцию с таким именем. Вот как вы-
глядит прототип упомянутой функции:
my_error_handler(int error_type, string error_msg
[, string errfile [, int errline [, array errcontext]]J))
Реализуемые этой функцией операции определяются исключительно вами, как
разработчиком.
540
Часть V. Реальные проекты на РНР и MySQL
Параметрами, передаваемыми этой функции, являются:
тип ошибки;
сообщение об ошибке;
файл, в котором возникла ошибка;
строка, в которой возникла ошибка;
таблица символов, то есть набор всех переменных вместе со значениями на
момент возникновения ошибки.
Рассмотрим возможные логические действия, которые может реализовать данная
функция:
Отображение заданного сообщения об ошибке (error_msg).
Сохранение информации в журнальном файле.
Отправка сообщения об ошибке по заданному адресу электронной почты.
Завершение сценария с помощью оператора exit.
Листинг 25.2 содержит сценарий, в котором обьявляется обработчик ошибок, ус-
танавливается обработчик ошибок с помощью функции set_error_handler (), а за-
тем генерируются некоторые ошибки.
Листинг 25.2 handle.php — этот сценарий объявляет пользовательский
обработчик ошибок и генерирует различные ошибки
<?php
// Функция обработки ошибок
function my_error_handler (Serrno, $errstr, $errfile, $errline)
{
echo ”<br /xtable bgcolor= ' ttcccccc ' xtrxtd>
<pxstrong>OIIMEKA:</strong> $errstr</p>
<р>Пожалуйста, попытайтесь еще раз, либо свяжитесь с нами
и сообщите, что за ошибка возникла в строке $errline файла '$errfile'</р>";
if (Serrno == E_USER_ERROR)
{
echo '<р>Эта ошибка является неисправимой, программа завершается</р>';
echo ' </tdx/trx/table>' ;
// Закрыть все открытые ресурсы, вывести нижний колонтитул страницы
//и так далее...
exit;
}
echo ' </td></trx/table>' ;
}
// Установить обработчик ошибок
set_error_handler('my_error_handler');
// Сгенерировать ошибки различных уровней
trigger_error('Вызвана функция trigger_error', E_USER_NOTICE);
fopen('nofile', 'r');
trigger_error('Этот компьютер уже на издыхании', E_USER_WARNING);
include ('nofile');
trigger_error('Этот компьютер самоуничтожится через 15 секунд', E_USER_ERROR) ;
Глава 25. Отладка
541
Вывод, полученный в результате выполнения сценария, показан на рис. 25.1.
Рис. 25.1. С использованием собственного обработчика ошибок
можно выводить более дружественные сообщения, нежели РНР
Данный пользовательский обработчик ошибок реализует лишь поведение по
умолчанию. Поскольку' этот код создается именно вами, в нем можно предпринимать
практически любые действия. Здесь можно сообщить посетителям страницы о воз-
никших неполадках, а также представить информацию так, чтобы она соответство-
вала общим концепциям оформления сайта. Что еще важнее, так это то, что предос-
тавляется существенная гибкость выбора дальнейших действий. Должен ли сценарий
продолжать выполнение? Сообщение будет протоколироваться в системных журна-
лах или же только отображаться? Требуется ли автоматическое уведомление службы
технической поддержки?
Важно отметить, что устанавливаемый обработчик ошибок не обязательно должен
охватывать все типы ошибок. Некоторые из них, такие как ошибки интерпретатора и
неисправимые ошибки времени выполнения, могут по-прежнему полагаться на стан-
дартную реакцию. Если это важно, следует тщательно проверять параметры до переда-
чи их в функцию, которая может генерировать неисправимые ошибки, и включать
собственный уровень ошибок E_USER_ERROR в том случае, когда если параметры вы-
зывают сбой. В РНР5 появилась новая возможность, состоящая в том, что если ваш
обработчик ошибок явно возвращает значение false, будет вызван встроенный об-
работчик РНР. В этом случае вы можете обрабатывать ошибки E_USER_* самостоя-
тельно, а обработку обычных ошибок поручить встроенному обработчику РНР.
Что дальше
В главе 26 мы приступим к работе над первым учебным проектом. Этот проект
должен продемонстрировать методы распознавания посетителей сайта, которые за-
ходили на него ранее, а также способы соответствующей настройки содержимого
сайта в соответствии с потребностями таких посетителей.
542
Часть V. Реальные проекты на РНР и MySQL
26
Реализация задачи
аутентификации
и персонализации
посетителей
В этом проекте будет реализована регистрация пользователей на Web-сайте. По-
сле решения упомянутой задачи станет возможным отслеживание интересов
посетителей и отображение для них соответствующим образом настроенного содер-
жимого. Такой подход носит название персонализации.
Данный проект дает посетителям возможность создать в Web-среде набор закладок
(bookmark) и предлагает им другие ссылки, которые могут заинтересовать посетите-
лей, исходя из их поведения в предыдущих сеансах. В более общем виде персонализа-
ция пользователей может применяться практически в любом Web-приложении, чтобы
отобразить для них желаемое содержимое, причем в предпочитаемом формате.
В этом, а также в последующих проектах мы начнем с обзора набора требований,
подобных тем, которые выдвигает заказчик сайта. Мы преобразуем эти требования в
набор компонентов решения, построим схему их объединения, а затем последова-
тельно реализуем каждый компонент.
В этом проекте будут реализованы следующие функциональные возможности:
Регистрация и аутентификация пользователей.
Управление паролями.
Запись предпочтений пользователей.
Персонализация содержимого.
Рекомендация содержимого в зависимости от имеющихся сведений о пользо-
вателе.
Задача
В рамках этого проекта задача сводится к созданию прототипа интерактивной сис-
темы закладок, которая называется PHPBookmark и подобна (но более функционально
ограничена) системе, доступной на сайте Backflip по адресу http:/ /backf lip. com.
Наша система должна предоставлять пользователям возможность регистрироваться
в ней и сохранять свои персональные закладки. а также получать рекомендации отно-
сительно других сайтов, подобранных на основе существующих предпочтений.
Требования к системе можно разбить на три основных группы.
Во-первых, необходимо иметь возможность идентифицировать отдельных посе-
тителей. Кроме того, следует реализовать какой-нибудь метод их аутентификации.
Во-вторых, необходимо иметь возможность хранения закладок для отдельного
посетителя. Пользователи должны иметь возможность как добавлять, так и
удалять закладки.
В-третьих, требуется располагать способом рекомендации пользователям сай-
тов, исходя из доступных сведений о клиентах.
Компоненты решения
Теперь, когда требования к системе известны, можно приступать к разработке
проекта решения и его компонентов. Рассмотрим возможные решения для каждого
из трех главных требований, перечисленных в предыдущем разделе.
Идентификация и персонализация пользователей
Как упоминалось ранее, существует несколько альтернатив аутентификации поль-
зователей. Поскольку' с пользователем необходимо связать некоторую личную ин-
формацию, входное имя и пароль будут храниться в базе данных MySQL и приме-
няться для решения задачи аутентификации.
Если необходимо предоставить пользователям возможность входить в систему, ука-
зывая свое имя и пароль, возникает потребность в наличии следующих компонентов:
Пользователи должны иметь возможность регистрировать выбранное имя и
пароль. Необходимо определить ограничения относительно длины и формата
имени и пароля. Из соображений безопасности пароли должны храниться в
зашифрованном виде.
Пользователям необходимо позволить входить в систему с указанием сведений,
которые они предоставили в процессе регистрации.
Пользователи должны иметь возможность выходить из системы после заверше-
ния работы с сайтом. Это не особенно важно для лиц. посещающих сайт из до-
машних компьютеров, но весьма существенно с точки зрения степени безопас-
ности, когда доступ к сайту осуществляется из компьютера общего пользования.
Для сайта необходима возможность проверки, вошел ли пользователь в систе-
му, а также предоставления данных тем. кто эту процедуру выполнил.
Пользователи должны иметь возможность изменять пароль для усиления сте-
пени защищенности.
Иногда пользователи забывают свои пароли. Им следует предоставить воз-
можность переустанавливать пароль без помощи администратора. Обычный
метод состоит в отправке пароля пользователю по адресу электронной почты,
указанному во время регистрации. Это означает необходимость сохранения
адреса электронной почты в процессе регистрации. Поскольку пароли хранят-
544
Часть V. Реальные проекты на РНР и MySQL
ся в зашифрованном виде и дешифрация их невозможна, реально потребуется
сгенерировать новый пароль и отправить его пользователю.
Для целей этого проекта мы разработаем функции, реализующие все перечислен-
ные выше возможности. Большинство функций могут повторно использоваться в
других проектах, причем вообще без изменений либо же с небольшими уточнениями.
Хранение закладок
Для хранения пользовательских закладок необходимо подготовить некоторое
пространство в базе данных MySQL. Потребуется реализовать следующую функцио-
нальность:
Пользователи должны иметь возможность извлекать и просматривать свои за-
кладки.
Пользователи должны иметь возможность добавлять новые закладки. При этом
необходимо проверять, что закладки являются допустимыми URL-адресами.
Пользователи должны иметь возможность удалять закладки.
Рекомендация закладок
При выборе рекомендованных закладок для конкретного пользователя можно
применять различные подходы. Можно выбирать наиболее популярные либо самые
популярные в конкретной области закладки. В данном проекте будет реализована
система рекомендаций, основанная на принципе “сходства образа мышления”. Эта
система выполняет поиск пользователей, имеющих ту же закладку, что и у вошедшего
в систему посетителя, и предлагает ему остальные закладки этих пользователей. Дабы
не рекомендовать строго персональные закладки, выбираются лишь те из них, кото-
рые хранятся более чем у одного пользователя.
Для реализации упомянутой функциональности будет написана еще одна функция.
Обзор решения
После составления ряда эскизов мы получили блок-схему, которая показана на
рис. 26.1.
Для каждого элемента блок-схемы будет построен собственный модуль. Некото-
рые модели потребуют одного сценария, а другие — двух. Кроме того, будут подготов-
лены библиотеки функций для реализации следующих задач:
Аутентификация пользователей.
Хранение и извлечение закладок.
Проверка данных на допустимость.
Соединение с базой данных.
Вывод в окно браузера. Генерация HTML-кода будет возложена на библиотек)'
функций. Это обеспечит единообразие визуального представления в рамках
всего сайта. (В этом-то и состоит принцип API-интерфейса — разделение логи-
ки и содержимого.)
Кроме того, для системы потребуется создать базу данных на сервере.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
545
Рис. 26.1. Эта диаграмма показывает возможные логические пути в системе PHPBookmark
Проект будет рассматриваться достаточно подробно, к тому же полный исходный
код приложения доступен в каталоге chapter26 на прилагаемом к книге компакт-
диске. Перечень подключаемых файлов приводится в табл. 26.1.
Таблица 26.1. Файлы приложения PHPBookmark
Имя файла Описание
bookmarks.sql login.php register_form.php register_new.php forgot_form.php forgo t_pas swd.php member.php add_bm_form.php add_bms.php delete_bms.php SQL-операторы для создания базы данных PHPBookmark. Титульная страница система с формой входа в систему. Форма регистрации пользователей в системе. Сценарий обработки новых регистрационных записей. Форма, заполняемая пользователями, забывшими пароль. Сценарий переустановки забытых паролей. Главная страница пользователя с представлением всех текущих загк^гш Форма для добавления новых закладок. Сценарий добавления новых закладок в базу данных. Сценарий удаления выбранных закладок из списка, связанного с кон- кретным пользователем.
recommend.php Сценарий выдачи рекомендаций, основанных на пользователях со сходными интересами.
change_passwd_form.php change_passwd.php logout.php bookmark_fns.php data_valid_fns.php db_fns.php user_auth_fns.php url_fns.php output_fns.php bookmark.gif Форма, заполняемая пользователями, желающими сменить пароль. Сценарий смены пароля в базе данных. Сценарий выхода пользователя из приложения. Набор подключаемых модулей для приложения. Функции проверки допустимости данных, вводимых пользователем. Функции для подключения к базе данных. Функции аутентификации пользователей. Функции добавления и удаления закладок, а также выработки рекомендаций. Функции, форматирующие вывод в виде HTML-кода. Логотип приложения PHPBookmark.
546
Часть V. Реальные проекты на РНР и MySQL
Начнем с реализации базы данных MySQL, поскольку она необходима для реали-
зации почти всей функциональности приложения.
Затем мы приступим к последовательному написанию кода. Начнем мы с титуль-
ной страницы, далее перейдем к аутентификации пользователей, хранению и извле-
чению закладок, и, наконец, завершим процедурой выработки рекомендаций. Эта
последовательность вполне логична — определяются зависимости и создаются в пер-
вую очередь элементы, которые впоследствии понадобятся для других модулей.
Примечание
Для того чтобы код проекта работал в соответствии с описанием, необходимо включить режим ма-
гических кавычек. Если это не выполнено, к вводимым данным в базу данных MySQL потребуется
применять функцию addslashes (), а к извлекаемым данным — функцию stripslashes ().
Режим магических кавычек уже использовался в качестве удобного сокращения. Кроме того, для
нормальной работы приложения понадобится браузер с поддержкой JavaScript.
Реализация базы данных
База данных PHPBookmark описывается достаточно простой схемой. Необходимо
хранить имена пользователей, их адреса электронной почты и пароли. Кроме того,
следует хранить URL-адреса закладок. Один пользователь может иметь множество
закладок, а одну и ту же закладку может зарегистрировать несколько пользователей.
Поэтому, очевидно, что база данных должна содержать две таблицы — для пользова-
телей (user) и для закладок (bookmark), как показано на рис. 26.2.
user
username passwd email
laura 7cbf26201e73c29b laura@tangledweb.com.au
luke 1fef10690eeb2e59 luke@tangledweb.com.au
bookmark
username bm_URL
laura laura http://slashdot.org http://php.net
Рис. 26.2. Схема базы данных для системы PHPBookmark
Таблица user хранит имя пользователя (оно же и является первичным ключом),
пароль и адрес электронной почты.
Таблица bookmark хранит пары “имя пользователя — закладка” (bm_URL). Каждое
имя пользователя в этой таблице ссылается на соответствующее имя пользователя в
таблице user.
Листинг 26.1 содержит SQL-код для создания этой базы данных, а также одного
пользователя для подключения к ней из среды Web. Если этот код планируется при-
менять в своей системе, его следует отредактировать — заменить пароль пользователя
на более надежный!
Листинг 26.1. bookmark. sql — SQL-файл для создания базы данных закладок
create database bookmarks;
use bookmarks;
Глава 26. Реализация задачи аутентификации и персонализации посетителей
547
create table user (
username varchar(16) not null primary key,
passwd char(40) not null,
email varchar(lOO) not null
) ;
create table bookmark (
username varchar(16) not null,
bm_URL varchar(255) not null,
index (username),
index (bm_URL),
primary key (username, bm_URL)
);
grant select, insert, update, delete
on bookmarks.*
to bm_user@localhost identified by 'password';
Эту базу данных можно создать, выполнив данный набор команд после регистра-
ции в качестве привилегированного (root) пользователя MySQL. Воспользуйтесь
следующей командной строкой:
mysql -u root -р < bookmarks.sql
Затем будет предложено ввести пароль.
Теперь, когда база данных готова, давайте приступим к реализации базового вари-
анта сайта.
Реализация базового варианта сайта
Первая страница, которую мы сейчас разработаем, будет называться login.php,
поскольку она предоставляет пользователям возможность входа в систему. Код пер-
вой страницы показан в листинге 26.2.
Листинг 26.2. login.php — титульная страница системы PJHPBookmark
require_once('bookmark_fns.php');
do_html_header('’);
display_site_info();
display_login_form();
do_html_footer();
Этот код выглядит очень простым, поскольку в нем, в основном, вызываются
функции из API-интерфейса, который мы разработаем для данного приложения.
Подробное описание этих функций можно найти ниже. Несложно заметить, что вы-
полняется включение файла (содержащего функции), а затем вызываются функции
визуализации HTML-заголовка, отображения содержимого и отображения нижнего
колонтитула страницы.
Вывод сценария показан на рис. 26.3.
548
Часть V. Реальные проекты на РНР и MySQL
Рис. 26.3. Титульная страница системы PHPBookmark,
сгенерированная функциями визуализации HTML-кода
из файла login.php
Функции системы помещены в файл bookmark_fns.php, содержимое которого
приведено в листинге 26.3.
Листинг 26.3. bookmark fns .php — включаемый файл с функциями для приложения
PHPBookmark
<?
// Этот файл можно включать во всех остальных файлах. В результате
// каждый файл будет содержать все необходимые функции и исключения.
require_once('data_valid_fns.php');
require_once('db_fns.php');
require_once('user_auth_fns.php');
require_once('output_fns.php');
require_once('url_fns.php');
Как видите, этот файл служит лишь контейнером для пяти других включаемых
файлов, которые будут использоваться в нашем приложении. Данная структура про-
екта объясняется тем, что функции разбиты на логические группы. Некоторые из
групп могут применяться в других проектах, поэтому каждая группа помещается в
отдельный файл. Мы создали файл bookmark—fns. php потому, что большая часть
функций в упомянутых пяти файлах будет использоваться в большинстве сценариев
приложения PHPBookmark. Гораздо проще включать один файл в каждый сценарий,
нежели указывать целых пять операторов require.
В нашем конкретном случае используются функции из файла output_fns. php.
Они реализуют вывод простого HTML-содержимого. Данный файл содержит четыре
функции, которые уже были задействованы в файле login.php — do_html_header(),
display—site_info(), display—login_form() и do_html_footer(), а также ряд дру-
гих функций.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
549
Мы не будем подробно исследовать абсолютно все функции, а просто рассмотрим
в качестве примера одну из них. Код функции do_html_header () представлен в лис-
тинге 26.4.
Листинг 26.4. Функция do_html_header () из библиотеки output_f ns. php — эта функция
выводит стандартный заголовок, который отображается на каждой странице приложения
function do_html_header(Stitle)
{
// Вывод HTML-заголовка
<html>
<head>
<title><?php echo Stitle;?></title>
<style>
body { font-family: Arial, Helvetica, sans-serif; font-size: 13px }
li, td { font-family: Arial, Helvetica, sans-serif; font-size: 13px }
hr { color: #3333cc; width=300; text-align=left}
a { color: #000000 }
</style>
</head>
<body>
<img src='bookmark.gif' alt='Логотип PHPbookmark' border=0
align='left' valign='bottom' height=55 width=57 />
<hl> PHPbookmark</hl>
<hr />
<?php
if($title)
do_html_heading(Stitle) ,-
Несложно заметить, что вся логика функции do_html_header () сводится к добав-
лению заголовка и логотипа к странице. Остальные функции, которые мы использо-
вали в файле login.php, подобны данной. Функция display_site_info () добавляет
текстовое описание к сайту; display_login_form() отображает форму входа в систе-
му, показанную на рис. 26.3; do_html_footer () включает в страницу стандартный
нижний HTML-колонтитул.
Преимущества изоляции либо удаления HTML-кода из главного потока логики об-
суждались в главе 24. Здесь мы будем использовать подход, основанный на API-
интерфейсе функций.
На рис. 26.3 хорошо видно, что страница предоставляет три опции — пользова-
тель может зарегистрироваться, войти в систему, если он уже зарегистрирован, ли-
бо же переустановить пароль, если он его забыл. Реализация этих модулей рассмат-
ривается в следующем разделе, который посвящен аутентификации пользователей.
Реализация аутентификации пользователей
Модуль аутентификации пользователей содержит четыре главных элемента: реги-
страцию пользователей, вход и выход из системы, смену паролей и переустановку
паролей. Рассмотрим по очереди все элементы.
550
Часть V. Реальные проекты на РНР и MySQL
Регистрация
Чтобы зарегистрировать пользователя, необходимо через форму получить сведе-
ния о нем и поместить их в базу данных.
Когда пользователь выполняет щелчок на ссылке Зарегистрироваться (Not а
member?), которая находится на странице login.php. для него выводится форма ре-
гистрации, сгенерированная сценарием register_form.php. Код этого сценария
представлен в листинге 26.5.
Листинг 26.5. register_form.php — эта форма дает пользователям возможность
зарегистрироваться в системе PHPBookmark
<?php
require_once('bookmark_fns.php');
do_hcml_header('Регистрация пользователей1);
display_registration_form();
do_html_footer();
Эта страница также достаточно проста и осуществляет лишь вызов функций
из библиотеки поддержки вывода — output_fns .php. Вывод сценария можно ви-
деть на рис. 26.4.
Форма с фоном серого цвета на этой странице представляет собой вывод функции
display_registration_form(), которая содержится в файле output_fns .php. Когда
пользователь выполняет щелчок на кнопке Регистрация (Register), выполняется сце-
нарий register_new.php, код которого показан в листинге 26.6.
Рис. 26.4. Регистрационная форма извлекает сведения,
необходимые для занесения в базу данных. Во избежа-
ние ошибок пользователям предлагается ввести пароль
два раза
Глава 26. Реализация задачи аутентификации и персонализации посетителей
551
Листинг 26.6. register new.php — этот сценарий проверяет допустимость вводимой
пользователем информации и помещает ее в базу данных
<?php
// Включить файлы функций для данного приложения
require_once('bookmark_fns.php');
// Создать короткие имена переменных
$email = $_POST[1 email ’ ];
$username=$_POST['username'];
$passwd=$_POST['passwd'];
$passwd2=$_POST['passwd21];
// Запустить сеанс, который может потребоваться позже.
// Его следует запустить сейчас, поскольку он должен
// находиться перед заголовками.
session_start();
try
{
// Проверить, заполнены ли поля формы
if (!filled_out($_POST))
{
throw new Exception('Вы не заполнили корректно форму. Пожалуйста,
. 'вернитесь на форму и повторите попытку. ' ) ;
}
// Недопустимый адрес электронной почты
if (!valid_email($email))
{
throw new Exception('Недопустимый адрес электронной почты. Пожалуйста,
.'вернитесь на форму и повторите попытку. ' ) ;
}
// Несовпадающие пароли
if ($passwd != $passwd2)
{
throw new Exception('Введенные пароли не совпадают. Пожалуйста,
.'вернитесь на форму и повторите попытку.');
}
// Проверить длину пароля.
if (strlen($passwd) < 6)
{
throw new Exception('Пароль должен иметь не менее 6 символов. '
.'Пожалуйста, вернитесь на форму и повторите попытку.');
}
// Проверить длину имени пользователя,
if (strlen($username) > 16)
(
throw new Exception!'Имя пользователя должно иметь не более 16 символов.'
.' Пожалуйста, вернитесь на форму и повторите попытку.’);
}
552
Часть V. Реальные проекты на РНР и MySQL
// Предпринять попытку регистрации. Эта функция также может
// сгенерировать исключение
register(Susername, $email, Spasswd);
// Зарегистрировать переменную сеанса
$_SESSION['valicLuser'] = $username;
// Вывести ссылку на страницу, предназначенную для
// зарегистрированных пользователей
do_html_header('Успешная регистрация');
echo 'Ваша регистрация прошла успешно. Переходите на страницу '
.'для зарегистрированных пользователей '
.'и приступайте к созданию закладок!';
do_html_ur1('member.php',
'Перейти на страницу для зарегистрированных пользователей');
// Конец страницы
do_html_footer();
}
catch (Exception $e)
{
do_html_header('Проблема:');
echo $e->getMessage();
do_html_footer();
exit;
}
Это первый более-менее сложный сценарий, с которым мы встретились в данном
приложении. Он начинается с включения файлов функций и запуска сеанса. (После
регистрации пользователя создается переменная сеанса, содержащая имя пользова-
теля, как это имело место в главе 22.)
Тело сценария помещено в блок try. поскольку выполняется проверка множества
условий. Если какое-то из условий завершается неудачно, выполнение переходит на
блок catch, который мы вскорости рассмотрим.
Затем осуществляется проверка допустимости данных, введенных пользователем.
Нам необходимо выполнить множество проверок:
Проверить, что форма полностью заполнена. Для этого применяется функция
filled_out():
if (!filled_out($_POST))
Эту функцию мы написали самостоятельно. Она содержится в библиотеке
data_valid_fns .php и будет рассматриваться чуть позже.
Проверить допустимость предоставленного адреса электронной почты:
if (valid_email($email))
Эта функция также написана нами и содержится в библиотеке data_val id_fns. php.
Проверить идентичность обоих вариантов пароля, введенных пользователем:
if ($passwd != $passwd2)
Глава 26. Реализация задачи аутентификации и персонализации посетителей
553
Проверить имя пользователя и пароль на допустимую длину:
if (strlen($passwd) < 6)
и
if (strlen($username) > 16)
В нашем примере длина пароля должна составлять не менее шести символов,
чтобы его было сложнее угадать, а длина имени пользователя должна быть не
более 16 символов, чтобы он уместился в базе данных. Обратите внимание, что
максимальная длина пароля ничем не ограничивается, поскольку в базе данных
хранится его хеш-значение SHA1, длина которого всегда 40 символов вне зави-
симости от длины исходного пароля.
Использованные выше функции проверки допустимости данных filled_out() и
valid_email () показаны, соответственно, в листинге 26.7 и 26.8.
Листинг 26.7. Функция f illed_out () из библиотеки data.validfns .php —
эта функция проверяет, заполнена ли форма
function filled_out($form_vars)
{
// Проверить, что каждая переменная имеет значение
foreach ($form_vars as $key => $value)
{
if (!isset($key) || (Svalue == ''))
return false;
}
return true;
}
Листинг 26.8. Функция valid_ema.il () из библиотеки data_valid_fne.php —
эта функция проверяет допустимость адреса электронной почты
function valid_email($address)
{
// Проверить допустимость адреса электронной почты
if (ereg(1л[a-zA-ZO-9 \+@([a-zA-ZO-9][a-zA-zC-9\-]*\.)+
[a-zA-Z]+$', $address))
return true;
else
return false;
j
Функция filled_out() ожидает получить массив переменных — в общем случае
таковым может быть $_POST или $_GET. Если массив заполнен, функция возвращает
значение true, а в противном случае — false.
В функции valid_email () для проверки адресов электронной почты применяется
несколько более сложное регулярное выражение, нежели то, которое было предло-
жено в главе 4. Если адрес является допустимым, функция возвращает значение true,
в противном случае — false.
554
Часть V. Реальные проекты на РНР и MySQL
После проверки введенных пользователем данных можно предпринять попытку
зарегистрировать пользователя. Как видно в листинге 26.6, это выполняется следую-
щим образом:
register($username, $email, $passwd);
// Зарегистрировать переменную сеанса
$_SESSION[1valid_user'] = $username;
// Вывести ссылку на страницу, предназначенную для
// зарегистрированных пользователей
do_html_header('Успешная регистрация');
echo 1 Ваша регистрация прошла успешно. Переходите на страницу '
.'для зарегистрированных пользователей '
.'и приступайте к созданию закладок!';
do_html_url('member.php',
'Перейти на страницу для зарегистрированных пользователей');
// Конец страницы
do_html_footer();
Несложно заметить, что мы просто вызываем функцию register () и передаем ей
имя пользователя, адрес электронной почты и пароль, которые были введены в фор-
ме регистрации. В случае успешного исхода мы регистрируем имя пользователя как
переменную сеанса и выводим ссылку на главную страницу зарегистрированных
пользователей. (Если регистрация завершается неудачей, эта функция сгенерирует
исключение, которое будет перехвачего блоком catch.) Вывод сценария показан на
рис. 26.5.
Функция register () находится во включенной библиотеке user_auth_fns .php и по-
казана в листинге 26.9.
Рис. 26.5. Регистрация прошла успешно — посетитель мо-
жет перейти на страницу, которая предназначена для заре-
гистрированных пользователей
Глава 26. Реализация задачи аутентификации и персонализации посетителей
555
Листинг 26.9. Функция register () из библиотеки user_auth_fns .php — эта функция
предпринимает попытку ввода информации о новом пользователе в базу данных
function register($username, $email, $password)
// Регистрирует нового пользователя в базе данных.
// Возвращает либо true, либо сообщение об ошибке.
{
// Подключиться к базе данных
$conn = db_connect();
// Проверить, уникально ли имя пользователя
$result = $conn->query("select * from user where username='$username’");
if (!$result)
throw new Exception(1 Невозможно выполнить запрос к БД');
if ($result->num_rows > 0)
throw new Exception(1 Это имя пользователя уже занято — вернитесь
.'на форму регистрации и выберите другое имя.');
// Если все в порядке, сохранить информацию в БД
$result = $conn->query("insert into user values
('$username', shal('$password'), '$email')");
if (!$result)
throw new Exception('Невозможно сохранение в БД - пожалуйста, '
.'попытайтесь позже.');
return true;
Эта функция не содержит ничего особо нового — она осуществляет подключение к
созданной ранее базе данных. Если выбранное имя пользователя уже задействовано
либо база данных не может быть обновлена, функция генерирует исключение. В про-
тивном случае база данных обновляется и возвращается значение true.
Следует заметить, что подключение к базе данных реализуется через написанную
ранее функцию db_connect (). Эта функция просто обеспечивает единственную об-
ласть хранения имени пользователя и пароля для подключения к базе данных. Таким
образом, для изменения пароля для доступа в базу данных достаточно изменить толь-
ко один файл приложения. Код функции db_connect () приведен в листинге 26.10.
Листинг 26.10. Функция db_connect () из библиотеки db_fns.php — эта функция
выполняет подключение к базе данных MySQL
function db_connect()
{
$result = new mysqli(1 localhost1, 'bm_user', 'password', 'bookmarks');
if (!$result)
throw new Exception('Невозможно подключиться к серверу баз данных');
else
return $result;
Зарегистрированные пользователи могут входить и выходить из системы через
обычные страницы, предназначенные для этих целей. Они буду разработаны в сле-
дующих разделах.
556
Часть V. Реальные проекты на РНР и MySQL
Вход в систему
После того как пользователи внесут необходимые данные в форму, что обеспечи-
вает сценарий login.php (см. рис. 26.3) и отправят ее, должен запуститься сценарий
member. php. Этот сценарий обеспечивает вход в систему. Кроме того, этот же сценарий
отображает связанные с пользователями закладки. Это основная функциональность
оставшейся части приложения. Код упомянутого сценария показан в листинге 26.11.
Листинг 26.11. member .php — этот сценарии является основой всего приложения
<?php
// Включить файлы функций для данного приложения
reguire_once(1bookmark_fns.php');
session_start();
// Создать короткие имена переменных
$username = $_POST['username'];
$passwd = $_POST['passwd'];
if ($username && $passwd)
// Пользователь только что попытался войти в систему
{
try
{
login(Jusername, $passwd);
// Если пользователь записан в базе данных,
// зарегистрировать его идентификатор
$_SESSION['valid_user'] = $username;
}
catch (Exception $e)
{
// Неудачный вход в систему
do_html_header('Проблема:');
echo 'Вход в систему невозможен. '
.'Для просмотра этой страница необходимо войти в систему.';
do_html_url('login.php', 'Вход');
do_html_footer();
exit;
}
}
do_html_header('Домашняя страница');
check_valid_user();
// Извлечь все закладки, сохраненные этим пользователем
if ($url_array = get_user_urls($_SESSION['valid_user']));
display_user_urls($url_array);
// Вывести меню опций
display_user_menu();
do_html_footer();
Логика этого сценария должны быть узнаваемой, поскольку в нем используются
некоторые идеи из главы 22.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
557
Во-первых, выполняется проверка, осуществил ли пользователь переход из ти-
тульной страницы. Другими словами, заполнил ли он форм} входа в систем}'. Затем
предпринимается попытка пустить пользователя в систему:
if ($username && $passwd)
// Пользователь только что попытался войти в систему
(
try
{
login($username, Spasswd))
// Если пользователь записан в базе данных,
// зарегистрировать его идентификатор
$_SESSION['valid_user'] = $username;
J
Для входа в систему используется функция login (). Она содержится в библиотеке
user_auth_fns .php и рассматривается ниже.
Если попытка входа в систему оказывается успешной, сеанс будет зарегистриро-
ван, как это делалось ранее. При этом имя пользователя сохраняется в переменной
ceaHcavali d_u s е г.
Если все идет нормально, отображается страница, предназначенная для зарегист-
рированных пользователей:
do_html_header('Домашняя страница');
check_valid_user();
// Извлечь все закладки, сохраненные этим пользователем
if ($url_array = get_user_urls ($_SESSION[ 'valid_user'
display_user_urls($url_array);
// Вывести меню опций
display_user_menu();
do_html_footer();
Эта страница также формируется при помощи функций вывода. Легко заметить,
что в ней используется несколько новых функций — check_valid_user() из файла
user_auth_fns .php, get_user_urls() из файла url_fns.php и display_user_urls()
из файла output_fns.php. Функция check_valid_user () проверяет, связан ли с те-
кущим пользователем зарегистрированный сеанс. Она предназначена для пользова-
телей, которые открыли сеанс ранее, а нс только что вошли в систему.
Функция get_user_urls () извлекает закладки пользователя из базы данных, а
display_user_urls () отображает закладки в браузере.
Код функции check_valid_user () будет рассмотрен немного ниже, а код осталь-
ных двух функций — во время обзора процесса хранения и извлечения закладок.
Сценарий member .php завершает страницу выводом меню с использованием функ-
ции display_user_menu().
Пример вывода сценария member .php показан на рис. 26.6.
Теперь более подробно рассмотрим функции login () и check_valid_user (). Код
функции login () представлен в листинге 26.12.
558
Часть V. Реальные проекты на РНР и MySQL
Рис. 26.6. Сценарий member.php проверяет, вошел ли
пользователь в систему, извлекает и отображает его за-
кладки, а затем выводит меню
Листинг 26.12. Функция login() из библиотеки user_auth_fns .php — эта функция
проверяет сведения о пользователе в базе данных
function login($username, $password)
// Проверяет наличие имени пользователя и пароля в базе данных.
// Если они там содержатся, возвращается значение true,
//в противном случае генерируется исключение.
{
// Подключиться к базе данных
$conn = db_connect();
// Проверить уникальность имени пользователя
$result = $conn->query("select * from user
where username='$username'
and passwd = shal('$password')•);
if (!$result)
throw new Exception('Вход в систему невозможен1);
if ($result->num_rows > 0)
return true;
else
throw new Exception('Вход в систему невозможен');
)
Функция login () подключается к базе данных и проверяет в ней наличие комби-
нации имени и пароля для данного пользователя. Если эти записи присутствуют, воз-
вращается значение true, в противном случае, либо когда данные пользователя не
могут быть проверены, генерируется исключение.
Функция check_valid_user () не выполняет повторного подключения к базе данных,
однако проверяет, что с пользователем связан зарегистрированный сеанс. Другими
словами, пользователь вошел в систему ранее. Эта функция показана в листинге 26.13.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
559
Листинг 26.13. Функция check valid user () из библиотеки user auth fns.php —
эта функция проверяет, связан ли с пользователем допустимый сеанс
function check_valid_user()
// Определяет, вошел ли пользователь в систему и,
// если нет, выводит соответствующее уведомление
{
global $valid_user;
if (isset($_SESSION[’valid_user' ] ))
{
echo 'Вы вошли в систему под именем '
.stripslashes($_SESSION['valid_user'
echo "<br />";
}
else
{
// Пользователь не вошел в систему
do_html_heading("Проблема: ") ;
echo “Вы не вошли в систему.<Ьг />";
do_html_url('login.php', 'Вход');
do_html_footer();
exit;
}
j
Если пользователь не вошел в систему, функция укажет ему, что это необходимо
выполнить, чтобы данная страница отобразилась, и предоставит ему ссылку на стра-
ницу входа.
Выход из системы
Как видно на рис. 26.6, меню содержит ссылку Выход (Logout). Щелчок на этой ссыл-
ке приводит к вызову сценария logout. php. Код сценария показан в листинге 26.14.
Листинг 26.14. logout .php — этот сценарий завершает сеанс пользователя
<?php
// Включить файлы функций для этого приложения
require_once('bookmark_fns.php');
session_start();
$old_user = $_SESSION['valid_user'];
// Сохранить для проверки, если кто-то вошел в систему *ранее*
unset($_SESSION['valid_user']);
$result_dest = session_destroy();
// Начать вывод html-содержимого
do_html_header('Выход’);
if (!empty($old_user))
{
if ($result_dest)
{
// Если пользователь вошел в систему и теперь выходит из нее
echo 'Успешный выход из системы.<br />';
560
Часть V. Реальные проекты на РНР и MySQL
do_html_url('login.php', 'Вход');
}
else
{
// Пользователь вошел в систему и не может выйти из нее
echo 'Выход из системы невозможен.<Ьг />';
}
)
else
(
// Если пользователь не входил в систему, но каким-то образом
// попал на эту страницу
echo 'Вы не входили в систему, поэтому и выходить из нее не нужно.<Ьг />';
do_html_url (' login, php' , 'Вход'),-
)
do_html_footer() ;
?>
И снова этот код может показаться знакомым. В нем использованы некоторые
идеи, которые рассматривались в главе 22.
Смена паролей
Если пользователь выберет опцию меню Сменить пароль (Change Password), ото-
бразится форма, показанная на рис. 26.7.
Эта форма сгенерирована сценарием change_passwd_form.php. Он достаточно
прост и использует лишь функции библиотеки вывода, поэтому его код здесь не рас-
сматривается.
Рис. 26.7. Сценарии change_passwd_form.php предостав-
ляет форму для смены паролей
Глава 26. Реализация задачи аутентификации и персонализации посетителей
561
После отправки формы запускается на выполнение сценарий change_passwd.php,
код которого показан в листинге 26.15.
Листинг 26.15. change passwd. php — этот сценарий предпринимает попытку смены пароля
<?php
require_once('bookmark_fns.php');
session_start();
do_html_header ( ' Смена пароля' ) ,-
// Создать короткие имена переменных
$old_passwd = $_POST['old_passwd'];
$new_passwd = $_POST['new_passwd'];
$new_passwd2 = $_POST [' new_passwd2 ' ],-
try
{
check_valid_user();
if (!filled_out($_POST))
throw new Exception!'Вы не заполнили корректно форму. '
.'Пожалуйста, попытайтесь еще раз.';
if ($new_passwd != $new_passwd2)
throw new Exception!'Введенные пароли не совпадают. '
.'Изменение невозможно.';
if (strlen($new_passwd) < 6)
throw new Exception('Новый пароль должен иметь длину, как минимум, 1
.'б символов. Повторите попытку.';
// Попытка обновить БД
change_password ($_SESSION[' valid_user' ], $old_passwd, $new_jpasswd) ;
echo 'Пароль изменен.';
}
catch (Exception $e)
{
echo $e->getMessage();
}
display_user_menu();
do_html_footer();
Этот сценарий проверяет, вошел ли пользователь в систему (с помощью функции
check_valid_user ()), заполнил ли он форму ввода пароля (с использованием
f illed_out ()), введены ли в обоих полях одинаковые пароли и является ли длина
этих паролей допустимой. Как видите, ничего нового. Если все правильно, вызывает-
ся функция change_password ():
change_password($_SESSION['valid_user'], $old_passwd, $new_passwd);
echo 'Пароль изменен.';
Эта функция содержится в библиотеке user_auth_fns .php, а ее код показан в лис-
тинге 26.16.
562
Часть V. Реальные проекты на РНР и MySQL
Листинг 26.16. Функция change jpassword() из библиотеки userauthfns .php —
эта функция предпринимает попытку обновления пароля в базе данных
function change_password($username, $old_password, $new_password)
// Заменяет старый пароль новым.
!/ Возвращает значение true или генерирует исключение
{
// Если прежний пароль введен правильно, он заменяется новым и возвращается
// значение true, в противном случае генерируется исключение
login($username, $old_password);
$conn = db_connect();
$result = $conn->query( "update user
set passwd - shal('$new_password')
where username = '$username' n) ;
if (!$result)
throw new Exception('Пароль не может быть изменен.’);
else
return true; // Пароль успешно изменен
Эта функция проверяет правильность ввода прежнего пароля с помощью уже рас-
смотренной функции login (). Если пароль указан верно, функция соединяется с ба-
зой данных и обновляет пароль новым значением.
Переустановка забытых паролей
Помимо смены пароля необходимо предусмотреть еще одну часто возникающую си-
туацию, когда пользователь попросту забывает пароль. Обратите внимание, что титуль-
ная страница, выводимая login.php, содержит ссылку Забыли пароль? (Forgotten your
password?), которая предназначена для случаев подобного рода. Ссылка инициирует за-
пуск сценария forgot_form.php, который использует функции вывода для отображения
формы (рис. 26.8).
Рис. 26.8. Сценарий forgot_form.php выводит форму, в
которой пользователь может запросить переустановку и
отправку пароля по электронной почте
Глава 26. Реализация задачи аутентификации и персонализации посетителей
563
Этот сценарий очень прост. В нем используются лишь функции вывода, поэтому
его код здесь не рассматривается. После отправки формы вызывается сценарий
forgot_passwd.php, который заслуживает специального рассмотрения. Код этого
сценария показан в листинге 26.17.
Листинг 26.17. forgot passwd.php — этот сценарий переустанавливает пароль,
выбирая для него случайное значение, и отправляет новую версию пользователю
по электронной почте
<?php
require_once(’bookmark_fns.php');
do_html_header('Переустановка пароля');
// Создать короткие имена переменных
$username = $_POST['username'];
try
{
$password = reset_password($username);
notify_password($username, $password);
echo 'Новый пароль отправлен по адресу электронной почты, ’
.'который вы указали при регистрации.';
)
catch (Exception $е)
{
echo 'Пароль не может быть переустановлен. '
.'Пожалуйста, повторите попытку позже.';
)
do_html_url('login.php', 'Вход');
do_html_footer();
В сценарии задействованы две основных функции: reset_password () и
notify_password (). Давайте рассмотрим их по очереди.
Функция reset_password() генерирует случайный пароль и помещает его в базу
данных. Ее код можно найти в листинге 26.18.
Листинг 26.18. Функция reset password () из библиотеки user_auth_fns. php —
этот сценарий присваивает паролю случайное значение и отправляет его
пользователю по электронной почте
function reset_password($username)
// Устанавливает случайное значение для пароля.
// Возвращает новый пароль либо значение false в случае ошибки
{
// Получить случайное слово из словаря длиной от 6 до 13 символов
$new_password = get_random_word(6, 13);
if($new_password == false)
throw new Exception('Невозможно сгенерировать новый пароль.')
// Добавить к нему число от 0 до 999
// с целью небольшого улучшения
srand ((double) microtime() * 1000000);
$rand_number = rand(0, 999);
$new_password .= $rand_number;
564
Часть V. Реальные проекты на РНР и MySQL
11 Изменить пароль в базе данных или вернуть значение false
Sconn = db_connect();
$result = $conn->query( "update user
set passwd = shal('$new_password')
where username = 'Susername'”);
if (!$result)
throw new Exception('Невозможно изменить пароль.'); // Пароль не изменен
else
return $new_password; // Пароль успешно изменен
Функция reset_password() генерирует случайный пароль, получив случайное
слово из словаря с помощью функции get_random_word () и добавив к нему случайное
число от 0 до 999. Функция get_random_word() также содержится в библиотеке
user_auth_fns .php. Ее код показан в листинге 26.19.
Листинг 26.19. Функция get_random_word() из библиотеки user_auth_fns .php — эта
функция получает случайное слово из словаря, используемое при генерации пароля
function get_random_word($min_length, $max_length)
I/ Извлекает случайное слово из словаря в заданном диапазоне
// длины и возвращает его в качестве результата
{
// Сгенерировать случайное слово
Sword = '';
//Не забудьте изменить этот путь на тот,
// который будет соответствовать вашей системе
Sdictionary = '/usr/dict/words'; // словарь ispell
$fp = fopen($dictionary, ' r');
if(!$fp)
return false;
$size = filesize(Sdictionary);
// перейти на случайную позицию в словаре
srand ((double) microtime() * 1000000);
$rand_location = rand(0, $size);
fseek($fp, $rand_location);
// Получить из файла словаря следующее полное слово допустимой длины
while (strlen($word)< $min_length || strlen(Sword)>$max_length
|| strstr(Sword, ..))
{
if (feof($fp))
fseek($fp, 0); // если достигнут конец файла словаря,
// перейти на его начало
Sword = fgets($fp, 80); 11 пропустить первое слово, поскольку оно
// может оказаться неполным
Sword = fgets($fp, 80); // потенциальный пароль
1;
$word=trim(Sword); // выполнить усечение завершающих символов \п
// в результате функции fgets
return $word;
Глава 26. Реализация задачи аутентификации и персонализации посетителей
565
Функция работает только при наличии словаря. В системе Unix встроенная про-
грамма проверки орфографии ispell укомплектована словарем, который обычно
содержится в каталоге /usr/dict/words, как в нашем примере, или же в каталоге
/usr/share/dict/words. Если вам не удалось отыскать файл словаря ни в одном из
упомянутых мест, в большинстве систем имеется возможность поискать сто, набрав
следующую команду:
$ locate dict/words
Если используется другая операционная система либо же нет желания устанавли-
вать словарь, не стоит особенно переживать. Список слов, используемый програм-
мой ispell, можно загрузить из следующего сайта:
http://wordlist.sourceforge.net/
Этот сайт содержит также словари на многих языках (отличных от английского).
Поэтому для получения случайного слова, скажем, на норвежском языке или эспе-
ранто, можно загрузить соответствующий словарь. В файлах словарей каждое слово
содержится в отдельной строке, а разделителями служат символы новой строки.
Чтобы получить случайное слово из файла, выбирается случайная позиция в
диапазоне от 0 до значения размера файла, и затем из нее производится чтение.
В таком случае, вероятнее всего, будет прочитана часть слова, поэтому текущая
строка пропускается и выбирается следующее слово путем двукратного вызова
функции fgets ().
Эта функция обладает двумя интересными особенностями. Во-первых, если в про-
цессе поиска слова достигается конец файла, выполняется переход на его начало:
if (feof($fp))
fseek($fp, 0); // если достигнут конец файла словаря,
// перейти на его начало
Во-вторых, осуществляется поиск слова определенной длины. Проверяется длина
каждого слова, извлекаемого из словаря, при этом если она не находится в диапазоне
значений от $min_length до $max_length, поиск продолжается. В то же время, вы-
полняется анализ слова вместе с апострофами (одинарными кавычками), которые
оно может содержать. Конечно, мы могли бы отменить их перед использованием
слова, однако тем самым бы несколько усложнили процесс получения следующего
слова.
Давайте вернемся к функции reset_password (). После того как мы сгенерировали
новый пароль, происходит обновление базы данных и возврат нового пароля глав-
ному сценарию. Затем этот пароль передается в функцию notify_password(), кото-
рая отправит его пользователю по электронной почте.
Рассмотрим функцию notify_password (), код которой показан в листинге 26.20.
Листинг 26.20. Функция notify_password() из библиотеки user_auth_fnB .php —
эта функция отправляет пользователю по электронной почте новый пароль
function notify_password($username, $password)
// Уведомляет пользователя о том, что его пароль изменен
(
$conn = db_connect();
566
Часть V. Реальные проекты на РНР и MySQL
$result = Sconn->query("select email from user
where username='$username’ ") ;
if (!Sresult)
{
throw new Exception!'Адрес электронной почты не найден.');
}
else if ($result->num_rows == 0)
{
throw new Exception!'Адрес электронной почты '
.'не найден.'); // имя пользователя отсутствует в БД
}
else
{
$row = $result->fetch_object();
Semail = $row->email;
$from = "From: supportgphpbookmark \r\n";
$mesg = "Ваш пароль для входа в систему PHPBookmark изменен на Spassword \r\n"
."Пожалуйста, учтите это при будущем входе в систему. \г\п";
if (mail($email, 'Информация о входе в систему PHPBookmark', $mesg, Sfrom))
return true;
else
throw new Exception('He удается отправить электронную почту.');
)
}
Функция notify_password () по имени пользователя и новому паролю выполняет
поиск адреса электронной почты в базе данных и с помощью PHP-функции mail О
отправляет пароль пользователю по электронной почте.
Гораздо безопаснее предоставить пользователю действительно случайный пароль,
составленный из комбинации букв верхнего и нижнего регистра, чисел и знаков
пунктуации, вместо случайного слова и числа. Однако пароль вроде zigzag487 поль-
зователю будет проще читать и печатать, чем случайный набор символов. В таком
наборе зачастую трудно различать нули (0) и прописные буквы О, а также единицы (1)
и строчные буквы 1.
В нашей системе файл словаря содержит приблизительно 45 000 слов. Если даже
взломщик знает способ построения пароля и точное имя пользователя, то ему и тогда
придется угадать один из приблизительно 22 500 000 вариантов. Такой уровень безо-
пасности вполне достаточен для приложений данного типа, даже если пользователи
оставляют без внимания сообщения электронной почты с предложением сменить
пароль.
Реализация хранения и извлечения закладок
Наступило время ознакомиться с методами хранения, извлечения и удаления за-
кладок.
Добавление закладок
Для добавления закладок можно щелкнуть на ссылке Добавить закладку (Add ВМ) в
пользовательском меню. В результате отображается форма, показанная на рис. 26.9.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
567
Рис. 26.9. Сценарий add_bm_form.php предоставляет
форму для добавления закладок
Этот сценарий также прост и использует лишь функции вывода. Поэтому его код
здесь подробно не рассматривается. После отправки формы вызывается сценарий
add_bms. php, показанный в листинге 26.21.
Листинг 26.21. add bms - php — этот сценарий добавляет новые закладки на
персональную страницу пользователя
<?php
require_pnce('bookmark_fns.php') ;
session_ start () ;
// Создать короткие имена переменных
$new_url = $_POST['new_url'];
do_html_header('Добавление закладок');
try
{
check_valid_user() ;
if (1filled_out($_POST))
{
throw new Exception('Форма заполнена не полностью.');
}
// Проверить формат URL
if (strstr($new_url, 'http://')===false)
$new_url = 'http://'.$new_url;
// Проверить допустимость URL
if (!(Sfopen($new_url, 'r')))
throw new Exception('Недопустимый URL-адрес.');
// Попытаться добавить закладку
add_bm($new_url);
echo 'Закладка добавлена.';
568
Часть V. Реальные проекты на РНР и MySQL
// Получить закладки, сохраненные данным пользователем
if ($url_array = get_user_urls($_SESSION['valid_user']))
display_user_urls($url_array);
}
catch (Exception $e)
{
echo $e->getMessage();
}
display_user_menu();
do_html_footer();
Опять-таки, этот сценарий также выполняет проверку допустимости данных, за-
пись в базу данных и вывод информации.
Для проверки допустимости данных сначала с помощью функции filled_out()
определяется, заполнил ли пользователь форм}'. Затем выполняются две проверки
URL-адреса. Вначале с помощью функции strstr () мы определяем, начинается ли
адрес с последовательности http: //. Если нет, она добавляется в начало адреса. По-
сле этого осуществляется проверка, существует ли в действительности данный адрес.
Как упоминалось в главе 19, функция fopen () позволяет открыть URL-адрес, начи-
нающийся с последовательности http: //. Если открыть файл удается, мы предпола-
гаем, что URL-адрес корректен, и после этого вызываем функцию add_bm () для его
сохранения в базе данных.
Эта и другие функции, связанные с закладками, содержатся в библиотеке
url_fns .php. Код функции add_bm () представлен в листинге 26.22.
Листинг 26.22. Функция addbm() из библиотеки url_fns .php — эта функция заносит
в базу данных новую закладку
function add_bm($new_url)
{
11 Добавляет новую закладку в базу данных
echo "Попытка добавления ".htmlspecialchars($newur1).'<br />’;
$valid_user = $_SESSION['valid_user'];
$conn = db_connect();
// Проверить, существует ли такая закладка
$result = $conn->query(“select * from bookmark
where username='$valid__user'
and bm_URL='$new_url' ") ;
if ($result && ($result->num_rows>0))
throw new Exception('Такая закладка уже существует.');
// Вставить новую закладку
if (!$conn->query( "insert into bookmark values
('$valid_user', '$new_url')”))
throw new Exception(1 He удается вставить закладку в базу данных.1);
return true;
)
Глава 26. Реализация задачи аутентификации и персонализации посетителей
569
Как видите, функция add_bm () достаточно проста. Она проверяет, что данная за-
кладка не содержится в базе данных. (Хотя маловероятно, что закладка будет вво-
диться дважды, вполне возможен случай, когда пользователь обновляет страницу,
нажимая кнопку обновления в браузере.) Если закладка новая, она сохраняется в базе
данных.
Вернемся к сценарию add_bm.php. Как и в сценарии member.php, его последними
операциями являются вызовы функций get_user_urls () и display_user_urls ().
Эти функции будут рассматриваться ниже.
Отображение закладок
В сценарии member.php и функции add_bm() использовались функции
get_user_urls() и display_user_urls (). Они осуществляют, соответственно, из-
влечение закладок из базы данных и их отображение. Функция get_user_urls()
содержится в библиотеке url_fns.php, a display_user_urls () — в библиотеке
output_fns.php.
Код функции get_.user_urls () приведен в листинге 26.23.
Листинг 26.23. Функция get user_urls () из библиотеки url-fns .php — эта функция
извлекает закладки пользователя из базы данных
function get_user_urls(Susername)
{
// Извлекает из базы данных все сохраненные пользователем URL-адреса
Sconn = db_connect();
Sresult = $conn->query( "select bm_URL
from bookmark
where username = 'Susername'");
if (!Sresult)
return false;
// Создать массив URL-адресов
$url_array = array () ;
for ($count = 1; $row = $result->fetch_row(),- ++$count)
{
$url_array[Scount] = $row[0];
}
return $url_array;
L
Давайте кратко рассмотрим функцию get_user_urls (). Она принимает в качест-
ве параметра имя пользователя и извлекает для него закладки из базы данных. Функ-
ция возвращает массив URL-адресов либо значение false, если закладки не могут
быть извлечены.
Результирующий массив URL-адресов может передаваться из функции
get_user_urls () в функцию display_user_urls (). Это простая функция вывода
HTML-содержимого, выполняющая печать URL-адресов в привлекательном таблич-
ном формате. Она здесь не рассматривается. Если вы хотите посмотреть, как выгля-
дит вывод, вернитесь еще раз к рис. 26.6. Функция просто помещает URL-адреса в
форму. Рядом с каждым URL-адресом находится флажок, который позволяет поме-
570
Часть V. Реальные проекты на РНР и MySQL
тить закладку и затем удалить ее. А сейчас мы рассмотрим вопросы, связанные с уда-
лением закладок.
Удаление закладок
Когда пользователь помечает некоторые закладки с целью их дальнейшего удале-
ния и выбирает из меню опцию Удалить закладку (Delete ВМ), выполняется отправка
формы, которая содержит соответствующие URL-адреса. Каждый флажок генериру-
ется с помощью следующего кода функции display_user_urls ():
echo "ctdxinput type='checkbox' name=' del_me [ ] '
value='$url'x/td>”;
Именем каждого флажка является del_me [ ]. Это означает, что если форма запус-
кает PHP-сценарий, будет осуществляться доступ к массиву $del_me, который содер-
жит все удаляемые закладки.
Выбор опции Удалить закладку приводит к запуску сценария delete_bms .php, ко-
торый показан в листинге 26.12.
Листинг 26.26. delete bms .php — этот сценарий удаляет закладки из базы данных
<?php
require_once('bookmark_fns.php');
session_start();
11 Создать короткие имена переменных
$del_me = $_POST['del_me'];
$valid__user = $_SESSION[' valid_user1 ] ;
do_html_header('Удаление закладок');
check_valid_user();
if (!filled_out($_POST))
{
echo 'He выбрано ни одной закладки для удаления. '
.'Пожалуйста, повторите попытку.';
display_user_menu();
do_html_footer();
exit;
}
else
{
if (count($del_me) >0)
{
foreach($del_me as $url)
{
if (delete_bm($valid_user, $url))
echo 'Удалена '.htmlspecialchars($url).',<br />';
else
echo 'Невозможно удалить 1.htmlspecialchars($url).'.<br />';
}
}
else
echo 'He выбрано ни одной закладки для удаления';
1
Глава 26. Реализация задачи аутентификации и персонализации посетителей
571
// Получить закладки, сохраненные данным пользователем
if ($url_array = get_user_urls($valid_user));
display_user_urls($url_array);
display_user_menu();
do_html_footer();
?>
Сценарий начинается с уже традиционной проверки данных на предмет допусти-
мости. Когда выясняется, что пользователь выбрал несколько закладок для удаления,
их удаление выполняется в следующем цикле:
foreach($del_me as $url)
{
if (delete_bm($valid_user, $url))
echo 'Удалена '.htmlspecialchars($url).1.<br />';
else
echo 'Невозможно удалить '.htmlspecialchars($url).'.<br />';
}
Несложно заметить, что функция delete_bm() выполняет удаление закладки из
базы данных. Ее код можно найти в листинге 26.25.
Листинг 26.25. Функция delete bm() из библиотеки url_fns.php — эта функция
удаляет одну закладку из списка пользователя
function delete_bm($user, $url)
{
// Удаляет один URL-адрес из базы данных
$conn = db_connect();
// Удалить закладку
if (!$conn->query( "delete from bookmark
where username='$user' and bm_url='$url'"))
throw new Exception('Закладка не может быть удалена.’);
return true;
J
Функция delete_bm() также очень проста. Она предпринимает попытку удаления
из базы данных закладки, связанной с определенным пользователем. Следует отме-
тить, что необходимо удалить определенную пару “имя пользователя — закладка”. Ос-
тальные пользователи все еще могут хранить данную закладку.
Пример вывода сценария удаления можно посмотреть на рис. 26.10.
Как и в сценарии add_bms .php, после внесения изменений в базу данных ото-
бражается новый список закладок с помощью функций get_user_urls() и
display_user_urls().
Выработка рекомендаций
В завершение, мы переходим к сценарию recommend.php, который реализует ре-
комендацию ссылок.
572
Часть V. Реальные проекты на РНР и MySQL
Рис. 26.10. Сценарий удаления уведомляет пользователя
о том, какие закладки удалены, и отображает оставшиеся
Существует множество различных способов выработки рекомендаций. Мы решили
воспользоваться принципом “сходства образа мышления”. Другими словами, мы должны
выполнить поиск других пользователей, у которых хотя бы одна закладка совпадает с за-
кладкой данного пользователя. Принцип “сходства образа мышления” предполагает, что
их остальные закладки также могут представлять интерес для данного пользователя.
Простейший метод реализации этого подхода в SQL-запросе связан с использова-
нием подзапросов. Первый подзапрос выглядит следующим образом:
select distinct(Ь2.username)
from bookmark bl, bookmark b2
where bl.username='$valid_user'
and bl.username != b2.username
and bl. bm__URL = b2 . bm_URL
В этом запросе используются псевдонимы для соединения таблицы bookmark базы
данных с собой же — странная, но иногда полезная концепция. Предположим, что
действительно существуют две таблицы закладок — Ы и Ь2. В таблице Ы выбираются
данные по закладкам для текущего пользователя. В другой таблице просматриваются
закладки всех остальных пользователей. Выполняется поиск других пользователей
(Ь2 .username), имеющих закладку (то есть URL-адрес), совпадающую с закладкой те-
кущего пользователя (bl .bm_URL = Ь2 ,bm_URL). Их имена не должны совпадать с име-
нем текугцего пользователя (bl.username != Ь2.username).
Этот запрос выдаст список пользователей, интересы которых совпадают с инте-
ресами текущего пользователя. Воспользовавшись полученным списком, можно вы-
полнять поиск остальных закладок пользователей, представленных в списке, с помо-
щью следующего внешнего запроса:
select bm_URL
from bookmark
where username in
Глава 26. Реализация задачи аутентификации и персонализации посетителей
573
(select distinct(b2.username)
from bookmark bl, bookmark b2
where Ы.username='$valid_user'
and bl.username != b2.username
and bl.bm_URL = b2.bm_URL)
Второй подзапрос служит для фильтрации закладок текущего пользователя; если
пользователь уже имеет какую-то закладку, не имеет смысла рекомендовать ему ее еще
раз. Наконец, переменная $popularity добавляет некоторый элемент фильтрации —
не следует рекомендовать “слишком персональные” закладки. Выбираются лишь те
URL-адреса, которые сохранены определенным числом других пользователей. В ко-
нечном итоге запрос приобретает следующий вид:
select bm_URL
from bookmark
where username in
(select distinct(b2.username)
from bookmark bl, bookmark b2
where bl.username='$valid_user'
and bl.username != b2.username
and bl. bm_URL = b2 . bmJJRL)
and bm_URL not in
(select bm_URL
from bookmark
where username= ’ $valid._user' )
group by bm_url
having count(bm_url)>$popularity
Если ожидается регистрация в системе большого количества посетителей, можно
увеличить значение переменной $popularity, тем самым рекомендуя лишь такие
URL-адреса, которые были сохранены большим числом пользователей. Эти адреса
должны оказаться наиболее интересными и отвечать более широкому спектру инте-
ресов по сравнению с обычными Web-страницами.
Полный код сценария выработки рекомендаций приведен в листингах 26.26 и
26.27. Главный сценарий называется recommend. php (листинг 26.26). Он обращается к
функции recommend_urls () из библиотеки url_fns . php (листинг 26.27).
Листинг 26.26. recommend.php — этот сценарий предлагает пользователю ссылки,
которые могут заинтересовать пользователя
<?php
require_once(’bookmark_fns.php’);
session_start();
do_html_header (' Рекомендация URL-адресов' ) ;
try
{
check_valid_user();
$urls = recommend_urls($_SESSION['valid_user']);
display_recommended_urls($urls);
}
catch(Exception $e)
(
echo $e->getMessage();
}
574
Часть V. Реальные проекты на РНР и MySQL
display_user_menu () ,-
do_html_footer() ;
Листинг 26.27. Функция recommend urIs () из библиотеки url_fns.php — эта функция
вырабатывает рекомендации для конкретного пользователя
function recommend_urls($valid_user, Spopularity = 1)
{
"к попытаемся обеспечить для пользователей выдачу *полуинтеллектуальных*
рекомендаций. Если пользователи имеют URL-адрес, совпадающий с закладками
других пользователей, их могут заинтересовать и прочие URL-адреса,
которые имеют другие пользователи
Scor.n = db_connect();
Найти других пользователей, закладки которых
с;впадают с закладкой текущего пользователя.
В качестве простейшего способа исключения из рассмотрения
приватных страниц посетителей, а также для более совершенной
рекомендации мы устанавливаем минимальный уровень популярности.
Если Spopularity = 1, могут рекомендоваться лишь
адреса, сохраненные более чем одним пользователем
Squery = "select bm_URL
from bookmark
where username in
(select distinct(b2.username)
from bookmark Ы, bookmark b2
where bl.username='$valid_user'
and bl.username != b2.username
and bl.bm_URL = b2.bm_URL)
and bm_URL not in
(select bm_URL
from bookmark
where username=1$valid_user')
group by bm_url
having count (bm__url) >$popularity" ;
if f'. (Sresult = $conn->query($query)))
throw new Exception!1 He удается найти закладки для рекомендации.');
if Sresult->num_rows==0)
throw new Exception)'He удается найти закладки для рекомендации.');
Surls = array!);
Сформировать массив подходящих URL-адресов
for Scount=0; $row = $result->fetch_object(); $count++)
Surls[Scount] = $row->bm_URL;
return Surls;
Пример вывода сценария recommend. php показан на рис. 26.11.
Глава 26. Реализация задачи аутентификации и персонализации посетителей
575
Рис. 26.11. Сценарий рекомендует пользователю сайт
amazon. com. Этот адрес был сохранен, по меньшей мере,
двумя другими пользователями
Заключение и возможные расширения
В предшествующих разделах мы рассмотрели базовые функциональные возможно-
сти приложения PHPBookmark. Ниже представлен список возможных расширений:
Группирование закладок по темам.
Реализация в функции выдачи рекомендаций ссылки Добавить это к моим за-
кладкам (Add this to my bookmarks).
Выдача рекомендаций, основанных на наиболее популярных URL-адресах в ба-
зе данных либо на определенной теме.
Интерфейс для администрирования пользователей и тем.
Методы повышения “интеллектуальности” либо быстродействия закладок.
Дополнительная проверка вводимой пользователями информации на предмет
ошибок.
Что ж, экспериментируйте! Вряд ли можно найти лучший метод изучения.
Что дальше
В следующем проекте мы создадим покупательскую тележку, которая даст пользо-
вателям возможность просматривать сайт, добавлять товары, подсчитывать общую
сумму и, в конечном итоге, осуществлять электронный платеж за отобранные товары.
576
Часть V. Реальные проекты на РНР и MySQL
21
Разработка
покупательской тележки
В этой главе исследуются базовые методы создания покупательских тележек. Мы
добавим эту функциональность к базе данных “Буквофил”, которая разрабаты-
валась на протяжении второй части книги. Мы также рассмотрим и еще одну воз-
можность, связанную с установкой и использованием существующей покупательской
тележки на РНР с открытым исходным кодом.
Если вам еще не доводилось сталкиваться с термином “покупательская тележка”
(“shopping сап”), на который иногда еще ссылаются, как на “корзину для покупок”
(“shopping basket”), то мы заметим, что упомянутый термин описывает специальный
онлайновый механизм осуществления покупок. В процессе просмотра некоторого
онлайнового каталога товаров вы можете добавлять в свою тележку отдельные пози-
ции (наименования товаров). По завершении просмотра вы производите расчет с
онлайновым магазином, то есть, по сути дела, приобретаете товар, ранее помещен-
ный вами в тележку.
Для построения покупательской тележки будут реализованы следующие функцио-
нальные возможности:
База данных продуктов, которые будут продаваться в онлайновом магазине.
Онлайновый каталог товаров с разбивкой по категориям.
Покупательская тележка, позволяющая отслеживать товар, выбираемый поль-
зователем с целью его приобретения.
Сценарий окончательного расчета, который обрабатывает детали платежа и
доставки товаров.
Интерфейс администрирования.
Задача
Вероятно, вы помните базу данных “Буквофил”, которая разрабатывалась во вто-
рой части книги. В текущем проекте мы найдем ей применение. Создаваемая нами
система должна отвечать следующим требованиям:
Необходимо найти способ подключения базы данных к браузеру пользователя.
Пользователи должны иметь возможность просматривать товарные позиции
каталога, разбитые по категориям.
Пользователи должны иметь возможность выбирать товарные позиции из ка-
талога с целью дальнейшего приобретения. Необходимо располагать каким-
нибудь механизмом отслеживания выбранных позиций.
После завершения покупок должен выполняться подсчет общей суммы заказа,
прием сведений для доставки и обработка платежа.
Необходимо создать интерфейс администрирования сайта “Буквофил”. Адми-
нистратор сайта должен иметь возможность добавления и редактирования
информации о книгах и категориях.
Компоненты решения
Рассмотрим методы реализации каждого из ранее перечисленных требований.
Построение онлайнового каталога
Для хранения каталога магазина “Буквофил” база данных уже существует. Тем не
менее, для данного приложения потребуется внести некоторые изменения и добавле-
ния. Одно из них предполагает определение категорий книг, как гласят требования.
Кроме того, потребуется добавить в существующую базу данных информацию,
связанную с адресами доставки, условиями платежа и так далее.
Мы уже знаем, как средствами РНР реализовать интерфейс с базой данных
MySQL, потому эта часть решения не должна вызывать особых затруднений.
Кроме того, для завершения обработки заказов должны применяться транзакции.
Для этого потребуется преобразовать таблицы базы данных “Буквофил” к типу хра-
нения InnoDB. Сам процесс преобразования очень прост.
Отслеживание выбираемого товара
Существует два базовых метода отслеживания товаров, выбираемых посетителя-
ми. Один из них состоит в помещении выбираемых элементов в базу данных, а вто-
рой — в использовании некоторой переменной сеанса.
Использование переменной сеанса для отслеживания выбираемых элементов в
процессе переходов между страницами гораздо проще в реализации, поскольку не
требует постоянных запросов к базе данных. Кроме того, этот метод позволяет избе-
жать загромождения базы данных ненужными данными, поступающими от посетите-
лей, которые просто просматривают каталог и очень часто меняют свои решения.
Таким образом, нам потребуется разработать переменную сеанса или, возможно,
набор переменных для хранения выбранных пользователем элементов. Когда пользо-
ватель завершает посещение магазина и выполняет окончательный расчет, эта инфор-
мация помещается в базу данных в виде записи, регистрирующей транзакцию.
Кроме того, эти данные могут использоваться для отображения в углу страницы
текущего состояния тележки, чтобы посетитель в любой момент мог видеть пред-
стоящую сумму расходов.
578
Часть V. Реальные проекты на РНР и MySQL
Реализация платежной системы
В этом проекте мы добавляем только механизм приема заказа от посетителя и
сведений, касающихся доставки. Реальная обработка платежей здесь не выполня-
ется. Существует большое разнообразие платежных систем, при этом для каждой
из них характерна собственная реализация. Для целей данного проекта мы напи-
шем фиктивную функцию, которую впоследствии можно будет заменить интер-
фейсом к любой выбранной платежной системе.
Платежные системы обычно призваны действовать в определенных географичес-
ких районах. Функционирование различных интерфейсов обработки платежей в ре-
альном времени, в основном, подобно. Вам потребуется открыть торговый счет в
банке для карточек, которые будут приниматься для снятия с них денег. Какие пара-
метры потребуется передавать в платежную систему, должен указать поставщик этой
системы.
Платежная система будет передавать данные в банк и возвращать код успешного
выполнения либо один из множества различных кодов ошибок. В обмен на передачу
данных платежная система будет взимать плату за установку либо годовую плату, а
также сбор, основанный на количестве или сумме транзакций. Некоторые поставщи-
ки назначают определенную плату даже за отклоненные транзакции.
Платежной системе необходима информация о клиенте (например, номер кре-
дитной карточки), идентификационная информация от вас как владельца магазина
(чтобы указать, какой торговый счет будет кредитоваться), а также общая сумма
транзакции.
Сумму заказа можно извлечь из переменной сеанса покупательской тележки.
Окончательная информация по заказу будет занесена в базу данных, а переменная
сеанса после этого будет удалена.
Разработка интерфейса администрирования
Помимо платежной системы и прочих элементов, необходимо предусмотреть ин-
терфейс администрирования, который позволил бы добавлять, удалять и редактиро-
вать информацию о книгах и категориях в базе данных.
Одним из часто используемых элементов редактирования является изменение це-
ны товара (например, для специальных предложений или продаж со скидкой). Это
означает, что сохранение заказа клиента предусматривает и сохранение цены, кото-
рая должна быть заплачена за товар. Если записи отражают лишь позиции, зака-
занные каждым клиентом, и текущую цену каждого наименования, это существенно
усложнит систему бухгалтерского учета, доводя до “белого каления” главного бухгал-
тера (да и не только его). Кроме того, это означает, что когда клиент возвращает или
обменивает товар, возможно, ему будут возвращаться лишние денежные средства,
если, например, он приобретал что-то во время акции и затем по какой-то причине
отказался от покупки.
В данном примере мы не планируем создавать полноценный интерфейс отслежи-
вания заказов и реализации. Понятно, что вы сможете без особого труда добавить его
в систему, если в этом возникнет настоятельная необходимость.
Глава 27. Разработка покупательской тележки
579
Обзор решения
Давайте попытаемся свести все воедино. Су-
ществуют два основных представления системы:
пользовательское и администраторское. С уче-
том необходимых функциональных возможно-
стей подготовлены две блок-схемы системы — по
одной для каждого представления. Эти блок-
схемы показаны на рис. 27.1 и 27.2.
На рис. 27.1 показаны главные ссылки между
сценариями в той части сайта, которая касается
пользователя. Клиент сначала открывает глав-
ную страницу, в которой перечислены все кате-
гории книг на сайте. Отсюда можно перейти к
определенной категории книг, а затем и к ин-
формации по отдельной книге.
Мы предоставим пользователю ссылку, кото-
рая даст возможность добавить выбранную кни-
гу в тележку. На этапе работы с тележкой можно
произвести окончательный расчет и покинуть
магазин.
Рис. 27.1. Система “Буквофил” в по-
льзовательском представлении дает
возможность просматривать книги
по категориям вместе со сведения-
ми о них, добавлять книги в тележку
и приобретать их
На рис. 27.2 показан интерфейс администратора. Он содержит большее число
сценариев, но не особенно много нового кода. Эти сценарии позволяют администра-
тору входить в систему и добавлять новые книги и категории.
Простейший способ реализовать редактирование и удаление книг и категорий со-
стоит в том, чтобы отобразить для администратора несколько отличную версию ин-
терфейса пользователя сайта. Администратор по-прежнему будет иметь возможность
просматривать категории и книги, но вместо доступа к покупательской тележке он
может переходить к определенной книге или категории, а затем редактировать либо
удалять ее.
Рис. 27.2. Система “Буквофил” в администраторском представлении позволяет добавлять,
редактировать и удалять книги и категории
580
Часть V. Реальные проекты на РНР и MySQL
Разработка сценариев, одновременно пригодных как для обычных пользователей,
так и для администраторов, позволяет сэкономить время и трудозатраты.
Тремя основными модулями кода для данного приложения являются:
Каталог.
Покупательская тележка и обработка заказа (мы решили объединить здесь эти
функции, поскольку они тесно взаимосвязаны).
Администрирование.
Как и в предыдущем проекте (глава 26), мы планируем создать и использовать на-
бор библиотек функций. В этом проекте применяется API-интерфейс функций, по-
добный тому, который применялся в предыдущем проекте. Мы попытаемся объеди-
нить фрагменты кода, отвечающие за вывод HTML-содержимого, в одну библиотеку.
Это должно полностью соответствовать принципу разделения логики и содержимого
и, что еще важнее, такой подход упростит чтение и сопровождение кода.
Кроме того, потребуется внести небольшие изменения в базу данных “Буквофил”.
База данных book_sc (Shopping Cart — тележка для покупок) переименована для того,
чтобы отличать базу данных покупательской тележки от базы, созданной во второй
части книги.
Весь код данного проекта содержится на прилагаемом к книге компакт-диске.
Полный перечень файлов приложения можно найти в табл. 27.1.
Таблица 27.1. Файлы приложения покупательской тележки
Имя Модуль Описание
index.php Каталог Титульная страница сайта. Отображает список категорий системы.
show_cat.php Каталог Страница, которая отображает для посе- тителя все книги определенной категории.
show_book.php Каталог Страница, которая отображает для посети- теля информацию по определенной книге.
show_cart.php Покупательская тележка Страница, которая отображает для посети- теля содержимое покупательской тележки. Кроме того, она используется для добавления элементов в тележку.
checkout.php Покупательская тележка Страница, которая представляет пользо- вателю полную информацию по заказу. Она также принимает информацию, свя- занную с доставкой.
purchase.php Покупательская тележка Страница, которая принимает от пользова- теля информацию, касающуюся платежа.
process.php П окупательская тележка Сценарий, который обрабатывает данные платежа и добавляет заказ в базу данных.
login.php Администрирование Сценарий, который позволяет админист- ратору входить в систему для внесения изменений.
logout.php Администрирование Сценарий, который реализует выход адми- нистратора из системы.
Глава 27. Разработка покупательской тележки
581
Окончание табл. 27.1
Имя Модуль Описание
admin.php Администрирование Главное меню администрирования.
change_password_form.php Администрирование Форма, позволяющая администратору изменять свой пароль.
change_password.php Администрирование Сценарий, который изменяет пароль ад- министратора.
insert_category_form.php Администрирование Форма, позволяющая администратору добавлять в базу данных новую категорию.
insert_category.php Администрирование Сценарий, который вставляет новую кате- горию в базу данных.
insert_book_f orm.php Администрирование Форма, позволяющая администратору добавлять в систему новую книгу.
insert_book.php Администрирование Сценарий, который добавляет новую кни- гу в базу данных.
edit_category_form.php Администрирование Форма, позволяющая администратору редактировать категорию.
edit_category.php Администрирование Сценарий, который обновляет категорию в базе данных.
edi t_book_f orm.php Администрирование Форма, позволяющая администратору редак- тировать детальную информацию о книге.
edi t_book.php Администрирование Сценарий, который обновляет информа- цию о книге в базе данных.
delete_category.php Администрирование Сценарий, который удаляет категорию из базы данных.
delete_book.php Администрирование Сценарий, который удаляет книгу из базы данных.
book_sc_fns.php Функции Набор включаемых файлов.
admin_fns .php Функции Набор функций, используемых сценария- ми администрирования.
book_fns.php Функции Набор функций хранения и извлечения данных о книгах.
order_fns.php Функции Набор функций хранения и извлечения информации, связанной с заказом.
output_fns.php Функции Набор функций вывода HTML-содержимого.
data_valid_fns.php Функции Набор функций проверки допустимости вводимых данных.
db_fns.php Функции Набор функций для подключения к базе данных book_sc.
user_auth_fns.php Функции Набор функций аутентификации пользо- вателей-администраторов.
book_sc.sql SQL SQL-код для создания базы данных book_sc.
populate.sql SQL SQL-код для помещения тестовой инфор- мации в базу данных book_sc.
582
Часть V. Реальные проекты на РНР и MySQL
Давайте приступим к рассмотрению реализации каждого из перечисленных в
табл. 27.1 модулей.
Примечание
Это приложение содержит довольно-таки большой объем кода. Большая часть кода реализует
функциональные возможности, которые были изучены ранее (в частности, в главе 26). К этим
{ • частям кода относится хранение данных и извлечение информации из базы данных, а также
г аутентификация администраторе. Этот код рассматривается достаточно кратко, а вот основ-
ная часть времени уделяется собственно функциям работы с покупательской тележкой.
Чтобы код проекта работал, как было задумано, необходимо включить опцию magic_quotes_gpc.
Если этого не сделать, потребуется применять функцию addslashes () к вводимой в базу
| данных MySQL информации. Упомянутый режим служит в качестве удобного сокращения.
я Режим магических кавычек можно включать для отдельных каталогов с помощью следующей
директивы в файле .htaccess:
-h php_value magic_quotes_gpc on
Создание базы данных
Как упоминалось ранее, в базу данных “Буквофил”, представленную во второй час-
ти, должны быть внесены небольшие изменения.
SQL-код создания базы данных book_sc приведен в листинге 27.1.
Листинг 27.1. book ec. sql — SQL-код создания базы данных book sc
create database book_sc;
use book_sc;
create table customers
(
customerid int unsigned not null auto_increment primary key,
name char(60) not null,
address char(80) not null,
city char(30) not null,
state char(20),
zip char(10),
country char(20) not null
) type=InnoDB;
create table orders
(
orderid int unsigned not null auto_increment primary key,
customerid int unsigned not null references customers(customerid),
amount float(6,2),
date date not null,
order_status char(10),
ship_name char(60) not null,
ship_address char(80) not null,
ship_city char(30) not null,
ship_state char(20),
ship_zip char(10),
ship_country char(20) not null
) type=InnoDB;
Глава 27. Разработка покупательской тележки
583
create table books
(
isbn char(13) not null primary key,
author char(100),
title char(100),
catid int unsigned,
price float(4,2) not null,
description varchar(255)
) type=InnoDB;
create table categories
(
catid int unsigned not null auto_increment primary key,
catname char(60) not null
) type=InnoDB;
create table order_items
(
orderid int unsigned not null references orders(orderid),
isbn char(13) not null references books(isbn),
item_price float(4,2) not null,
quantity tinyint unsigned not null,
primary key (orderid, isbn)
) type=InnoDB;
create table admin
(
username char(16) not null primary key,
password char(40) not null
);
grant select, insert, update, delete
on book_sc.*
to book_sc@localhost identified by 'password';
Несмотря на то что оригинальный пользовательский интерфейс приложения “Бу-
квофил” подходит практически полностью, возникает несколько дополнительных
требований, которые вызваны необходимостью доступа к базе данных в онлайновом
режиме.
В первоначальную базу данных были внесены следующие изменения:
Добавлены дополнительные поля под адреса клиентов. Это особенно важно
сейчас, когда создается более реалистичное приложение.
Добавлен адрес доставки заказа. Контактный адрес клиента не всегда совпадает
с адресом доставки, особенно когда приобретаются подарки для кого-то.
Добавлена новая таблица категорий (categories), а в таблицу книг (books) до-
бавлено поле идентификатора категории (catid). Сортировка книг по катего-
риям должна упростить просмотр сайта.
В таблицу order_items добавлено поле item_price, которое хранит цену това-
ра. Тем самым мы учитываем возможность изменения цены товара. Необходи-
мо знать цену товара на момент, когда клиент его заказывает.
Добавлена таблица admin для хранения входного имени и пароля администратора.
584
Часть V. Реальные проекты на РНР и MySQL
Удалена таблица рецензий. Рецензии можно реализовать как расширение про-
екта. Вместо этого для каждой книги существует поле описания, которое со-
держит краткую аннотацию.
Изменен механизм хранения на InnoDB. В результате появляется возможность
использовать внешние ключи и транзакции.
Для того чтобы создать в системе базу данных, в среде MySQL потребуется выполнить
сценарий book_sc. sql, обладая правами привилегированного пользователя (root):
mysql -u root -p < book_sc.sql
(Потребуется ввести пароль привилегированного пользователя.)
До этого момента необходимо изменить пароль пользователя book_sc на что-
нибудь, отличное от 'password'. Не забудьте после изменения пароля в файле
book_sc.sql внести также соответствующие коррективы в файл db_fns.php.
(В каких конкретно местах должны вноситься изменения, мы рассмотрим чуть позже.)
Кроме того, мы подготовили файл тестовых данных, который имеет имя
populate. sql. Тестовые данные можно занести в базу данных, выполнив этот сцена-
рий в среде MySQL.
Реализация онлайнового каталога
Разрабатываемое приложение содержит три сценария, связанных с каталогом:
главная страница, страница категорий и страница информации о книге.
Титульная страница сайта генерируется сценарием index.php. Вывод этого сце-
нария можно видеть на рис. 27.8.
Рис. 27.3. На титульной странице сайта находится список кате-
горий книг, доступных для приобретения
Глава 27. Разработка покупательской тележки
585
Несложно заметить, что кроме списка категорий окно содержит кнопку вызова
покупательской тележки (в правом верхнем углу) и итоговые данные по содержимому
тележки. Эти элементы содержатся на каждой странице, открываемой в процессе
просмотра и выбора товара.
Как только пользователь выполняет щелчок на одной из категорий, открывается
страница категорий, генерируемая сценарием show_cat. php. Например, на рис. 27.4
показана страница категорий для книг, посвященных тематике Internet.
Рис. 27.4. Каждая книга категории сопровождается изображе-
нием обложки
Все книги из категории Internet представлены в виде списка ссылок. Когда поль-
зователь выполняет щелчок на одной из ссылок, открывается страница с информа-
цией о соответствующей книге, которая показана на рис. 27.5.
На этой странице, помимо кнопки View Cart (Тележка), находится кнопка Add to
Cart (Добавить в тележку), позволяющая выбирать товар. Мы вернемся к ней позже,
когда приступим к написанию кода собственно покупательской тележки.
Рассмотрим каждый из трех сценариев.
Вывод списка категорий
Первый сценарий, index. php, выводит список всех категорий из базы данных; код
этого сценария показан в листинге 27.2.
586
Часть V. Реальные проекты на РНР и MySQL
Рис. 27.5. С каждой книгой связана страница информации, со-
держащая краткое описание книги
Листинг 27.2. index.php — сценарий вывода титульной страницы сайта
<?php
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
do_html_header('Добро пожаловать в магазин БУКВОФИЛ!'),-
echo '<р>Пожалуйста, выберите категорию:</р>';
// Извлечь категории из базы данных
$cat_array = get_categories();
// Отобразить в виде ссылок на соответствующие страницы категорий
display_categories($cat_array);
// Если пользователь вошел в систему как администратор, вывести
// ссылки на добавление, удаление и редактирование категорий
if(isset($_SESSION['admin_user']))
{
display_button('admin.php', 'admin-menu', 'Меню администрирования');
)
do_html_footer();
Сценарий начинается с включения файла book_sc_fns.php, который содержит
все библиотеки функций для данного приложения.
Глава 27. Разработка покупательской тележки
587
После этого потребуется запустить сеанс, который необходим для корректного
функционирования покупательской тележки. Сеанс используется каждой страницей
сайта.
Сценарий включает вызовы функций вывода HTML-содержимого, таких как
do_html_header() и do_html_footer(), код которых находится в файле
output_fns.php.
Кроме того, предусмотрен код для проверки ситуации, когда пользователь входит
в систему с правами администратора. В этом случае такому пользователю предостав-
ляются несколько другие средства навигации. Мы вернемся к этому вопросу в разде-
ле, посвященном функциям администрирования.
Ниже показана наиболее важная часть сценария:
// Извлечь категории из базы данных
$cat_array = get_categories();
// Отобразить в виде ссылок на соответствующие страницы категорий
display_categories($cat_array);
Функции get_categories () и display_categories () находятся, соответственно, в
библиотеках book_fns.php и output_fns .php. Функция get_categories () возвращает
массив категорий, существующих в системе, который затем передается в функцию
display_categories (). Код функции get_categories () представлен в листинге 27.3.
Листинг 27.3. Функция get_categories () из библиотеки book_fns.php —
эта функция извлекает из базы данных список категорий
function get_categories()
{
// Запросить в базе данных список категорий
$сопп = db_connect();
$query = 'select catid, catname
from categories';
$result = $conn->($query);
if (!$result)
return false;
$num_cats = $result->num_rows;
if ($num_cats == 0)
return false;
$result = db_result_to_array($result);
return $result;
}
Как видите, функция get_categories () подключается к базе данных и затем из-
влекает список, включающий все идентификаторы и имена категорий. Здесь исполь-
зуется ранее написанная функция db_result_to_array () из библиотеки db_fns .php.
Эта функция, код которой показан в листинге 27.4, принимает идентификатор ре-
зультата от MySQL и возвращает массив строк с числовой индексацией, где каждая
строка представляет собой ассоциативный массив.
588
Часть V. Реальные проекты на РНР и MySQL
Листинг 27.4. Функция db_result_to_array() из библиотеки db .fns.php —
эта функция преобразует идентификатор результата MySQL в массив результатов
function db_result_to_array($result)
{
$res_array = arrayO;
for ($count = 0; $row = $result->fetch_assoc(); $count++)
$res,array[$count] = $row;
return $res_array;
j
В нашем случае этот массив возвращается в сценарий index.php, где, в свою оче-
редь, передается в функцию display_categories () из библиотеки output_fns .php.
Эта функция отображает каждую категорию в виде ссылки на страницу, содержащую
книги данной категории. Код функции показан в листинге 27.5.
Листинг 27.5. Функция di splay_categories () из библиотеки output_fns. php-
эта функция отображает массив категорий в виде списка ссылок на категории
function display_categories($cat_array)
{
if (!is_array($cat_array))
{
echo 'В настоящий момент нет доступных категорийсЪг />’;
return;
}
echo '<ul>’;
foreach ($cat_array as $row)
{
$url = 'show_cat.php?catid='.($row['catid' ]);
$title = $row['catname'];
echo '<li>';
do_html_url($url, $title);
echo '</li>
}
echo '</ul>';
echo '<hr />';
}
Функция display_categories() преобразует каждую категорию базы данных в
ссылку. Все ссылки передаются в следующий сценарий, show.cat.php, при этом каж-
дая из них имеет собственный параметр — идентификатор категории catid. (Это
уникальное число, сгенерированное MySQL, которое служит для идентификации ка-
тегории.)
Упомянутый параметр определяет, какая категория должна в конечном итоге ото-
бражаться.
Глава 27. Разработка покупательской тележки
589
Вывод списка книг, относящихся к заданной категории
Процесс вывода списка книг, относящихся к определенной категории, аналогичен
рассмотренному выше. Вывод осуществляет сценарий show_cat. php, который пока-
зан в листинге 27.6.
Листинг 27.6. show „cat .php — этот сценарий отображает книги определенной категории
<?php
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
$catid = $_GET['catid'];
$name = get_category_name($catid);
do_html_header($name);
// Извлечь из базы данных информацию о книге
$book_array = get_books($catid);
display—books($book_array);
// Если пользователь вошел в систему как администратор, вывести
// ссылки на добавление и удаление ссылок на книги
if(isset($_SESSION!'admin_user']))
{
display_button('index.php', 'continue', 'Продолжить покупки');
display_button('admin.php', 'admin-menu', 'Меню администрирования');
display_button (''edit_category_form.php?catid=$catid",
'edit-category', 'Редактировать категорию');
}
else
display_button('index.php', 'continue-shopping', 'Продолжить покупки');
do_html_footer () ,-
Структура этого сценария во многом подобна структуре сценария вывода титуль-
ной страницы, с той лишь разницей, что вместо категорий извлекаются книги.
Сначала, как обычно, запускается сеанс с помощью функции session_start (), а
затем с использованием функции get_category_name () передаваемый идентифика-
тор категории преобразуется в имя категории:
$name = get_category—name($catid);
Эта функция выполняет поиск имени категории в базе данных. Код функции
представлен в листинге 27.7.
Листинг 27.7. Функция get_category_name () из библиотеки book_fns.php —
эта функция преобразует идентификатор категории в имя категории
function get—category_name($catid)
{
// Запросить в базе данных имя категории для данного идентификатора категории
$catid = intval($catid);
$conn = db_connect();
590
Часть V. Реальные проекты не РНР и MySQL
$query = "select catname
from categories
where catid = $catid“;
$result = $conn->query($query);
if (!$result)
return false;
$num_cats = $result->num_rows;
if ($num_cats == 0)
return false;
$row = $result->fetch_object() ;
return $row->catname;
После извлечения имени категории мы можем вывести HTML-заголовок и перей-
ти к извлечению из базы данных списка книг, относящихся к выбранной категории:
$book_array = get_books($catid);
display_books($book_array);
Функции get_books() и display—books () во многом подобны функциям
get_categories () и display_categories (), поэтому они здесь детально не рассмат-
риваются. Единственное отличие состоит в том, что информация извлекается из таб-
лицы книг, а не таблицы категорий.
Функция display-books () создает ссылку на каждую книгу данной категории с ис-
пользованием сценария show_book. php. И снова, каждая ссылка сопровождается па-
раметром в виде суффикса. На этот раз он представляет собой ISBN конкретной книги.
В заключительном фрагменте сценария show_cat.php содержится код для ото-
бражения дополнительных функциональных возможностей в случае, когда в систему
входит администратор. Мы рассмотрим этот код в разделе, посвященном функциям
администрирования.
Вывод информации о конкретной книге
Сценарий show_book. php принимает номер ISBN в качестве параметра, а затем
извлекает и отображает детальные сведения о данной книге. Код этого сценария
приведен в листинге 27.8.
Листинг 27.8. ahoW-book. php — этот сценарий отображает данные по определенной книге
<?php
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
$isbn = $_GET['isbn'];
// Извлечь из базы данных информацию о конкретной книге
$book = get_book_details($isbn);
do_html_header($book[’title’]);
display_book_details($book);
Глава 27. Разработка покупательской тележки
591
// Установить url для кнопки "Продолжить"
$target = ’index.php';
if($book['catid'])
{
$target = 'show_cat.php?catid='.$book['catid'];
}
// Если пользователь вошел в систему как администратор, вывести
// ссылку на редактирование информации о книге
if( check_admin_user() )
{
display_button("edit_book_form.php? isbn=$ isbn",
'edit-item', 'Редактировать элемент');
di splay_button('admin.php', 'admin-menu', 'Меню администрирования');
display_button($target, 'continue', 'Продолжить');
}
else
{
display_button("show_cart.php?new=$isbn", 'add-to-cart', 'Добавить '
.$book['title'].' в мою тележку');
display_button($target, 'continue-shopping', 'Продолжить покупки');
}
do_html_footer();
Этот сценарий также очень похож на сценарии вывода двух ранее рассмотренных
страниц. Сначала, как всегда, запускается сеанс, а затем с помощью строки:
$book = get_book_details($isbn);
из базы данных извлекается информация о книге. Для вывода данных в HTML-
формате используется следующий вызов:
display_book_details($book);
Следует также отметить, что функция display_book_details () выполняет поиск
файла изображения для книги, имя которого выглядит как images/$isbn. jpg. Если
такой файл не существует, изображение не выводится.
Оставшаяся часть сценария show_book.php устанавливает средства навигации.
Обычному пользователю предоставляется кнопка Continue Shopping (Продолжить
покупки), возвращающая на страницу категорий, и кнопка Add to Cart (Добавить в
тележку) для добавления книги в покупательскую тележку. Если вошедший в систему
пользователь обладает правами администратора, ему предлагаются несколько иные
опции.
На этом обзор системы работы с каталогом можно считать завершенным. Давайте
перейдем к коду, который реализует функциональность покупательской тележки.
Реализация покупательской тележки
Функциональность покупательской тележки тесно связана с переменной сеанса
cart. Она представляет собой ассоциативный массив, в котором ключами служат но-
мера ISBN книг, а значениями — заказанное количество книг. Например, если в те-
592
Часть V. Реальные проекты на РНР и MySQL
лежку помещается один экземпляр данной книги, в массиве появляется следующая
запись:
067232525Х => 1
Приведенная запись означает один экземпляр книги с номером ISBN 067232525Х.
Когда книги помещаются в тележку, в массив добавляются элементы подобного рода.
Во время просмотра содержимого тележки мы будем использовать массив cart для
поиска в базе данных полной информации по книгам.
Кроме того, используются еще две переменных сеанса для управления отображе-
нием в заголовке данных по количеству элементов (Total Items) и сумме заказа (Total
Price) — соответственно, items и total_price.
Использование сценария show_cart .php
Обзор реализации покупательской тележки мы начнем со сценария show_cart. php.
Он выводит страницу, которая открывается после щелчка на кнопках View Cart либо
Add to Cart. Если сценарий show_cart. php вызывается без параметров, отображается
просто содержимое тележки. Если в качестве параметра передается какой-то номер
ISBN, книга, соответствующая этому номеру ISBN, добавляется в тележку.
Давайте сначала посмотрим на рис. 27.6, который должен много чего прояснить.
В данном случае пользователь выполнил щелчок на кнопке View Cart, когда тележка
была еще пуста. Другими словами, не выбрана еще ни одна позиция для покупки.
Рис. 27.6. Сценарий show_cart. php, вызванный без параметров,
просто выводит содержимое тележки
Глава 27. Разработка покупательской тележки
593
На рис. 27.7 показана тележка в несколько ином состоянии, когда для покупки вы-
браны две книги. В данном случае пользователь попал на эту страницу в результате
щелчка на кнопке Add to Cart в пределах страницы, сгенерированной сценарием
show_book. php для англоязычного варианта этой книги, РНР and MySQL Web Deve-
lopment. Если внимательно посмотреть на строку адреса в браузере, можно заметить,
что на сей раз сценарий вызывается с параметром. Параметр называется new и имеет
значение 067232525Х, то есть номер ISBN книги, только что помещенной в тележку.
л? Ваша гележка - Mozffla <Вм*к1 ID: 2004112206}
JOJxi
Qsae»® Вид Переход Заклада Инструменты Q&6 Справка Oebyg
‘ htlp://ioGrfret#^^ Поиск I
таЗаД Г4 , МОНОВИТЪ -- v..| < v 'л"'г ’ ' • •
BMk-O*Rama
Количество
Всего
Ваша тележка
Sams Teach Yourself РНР4 in 24 Hours by Matt Zandstra
Товар
Цена
PHP and MySQL Web Development by Luke Welling and Laura Thomson $49 99
Ji-------
$4999
$24 99
Г~
$2499
2
ЛМ4Ю
-M : ГОПЯО
Рис. 27.7. Сценарий show_cart.php, вызванный с параметром
new, помещает в тележку новый элемент
На этой странице находятся еще две опции. Первую из них, кнопку Save Changes
(Сохранить изменения), можно использовать для изменения количества элементов в
тележке. Для этого следует непосредственно изменить количество экземпляров в по-
лях количества и щелкнуть на кнопке Save Changes. По сути дела, это кнопка отправ-
ки формы, которая обеспечивает возврат в сценарий show_cart .php с целью обнов-
ления содержимого тележки.
Вторая опция, кнопка Go То Checkout (Окончательный расчет), выбирается посе-
тителем для того, чтобы покинуть магазин. Мы вернемся к ней немного позже.
Пока же мы рассмотрим код сценария show_cart. php, который показан в листин-
ге 27.9.
Листинг 27.9. show cart.php — этот сценарий управляет покупательской тележкой
<?php
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
@ $new = $_GET['new'];
594
Часть V. Реальные проекты на РНР и MySQL
if($new)
{
// Выбран новый элемент
if(!isset($_SESSION['cart' ] ))
{
$_SESSION[' cart'] = array();
$_SESSION['items'] =0;
$_SESSION['total—price'] ='0.00';
}
if(isset($_SESSION['cart'][$new]))
$_SESSION['cart'][$new]++;
else
$_SESSION['cart'][$new] = 1;
$_SESSION['total—price'] = calculate_price($_SESSION['cart']);
$_SESSION!'items'] = calculate—items($_SESSION!'cart']);
}
if(isset($_POST!'save']))
{
foreach ($_SESSION['cart'] as $isbn => $qty)
{
if($_POST[$isbn]=='0')
unset($_SESSION!'cart'][$isbn]);
else
$_SESSION!'cart'][$isbn] = $_POST[$isbn];
}
$_SESSION['total_price'] = calculate_price($_SESSION['cart' ] );
$_SESSION!'items'] = calculate_iterns($_SESSION['cart']);
}
dO—html—header('Ваша тележка');
i f($—SESSION['cart']&&array_count_values($_SESSION['cart' ]))
display_cart($_SESSION['cart' ]);
else
(
echo '<p>Bauia тележка пуста</р>';
echo '<hr />';
}
$target = 'index.php';
// Если в тележку был только что добавлен новый элемент,
// продолжить покупки товаров данной категории
if($new)
{
$details = get_book_details($new);
if($details['catid'])
$target = 'show_cat.php?catid='.$details['catid'];
}
display_button($target, 'continue-shopping', 'Продолжить покупки');
// Используйте это, только если настроено SSL-соединение
И $path = $_SERVER!'PHP_SELF'];
И $server = $_SERVER['SERVER-NAME'];
// $path = str_replace('show_cart.php', '', $path);
Глава 27. Разработка покупательской тележки
595
11 display-button('https://'.$server.$path.'checkout.php',
'go-to-checkout', 'Окончательный расчет');
// Используйте это, если SSL-соединение не установлено
display_button('checkout.php’, 'go-to-checkout', 'Окончательный расчет');
do_html_footer () ,-
Этот сценарий состоит из трех основных частей: вывод содержимого тележки,
добавление в нее элементов и сохранение изменений. Все части по порядку рассмат-
риваются в трех последующих разделах.
Вывод содержимого тележки
Содержимое тележки должно отображаться вне зависимости от страницы, на ко-
торой был произведен щелчок на кнопке View Cart. В общем случае, как только посе
тигель щелкнет на View Cart, единственной порцией кода, которая выполняется, бу-
дет следующая:
if($_SESSION['cart']&&array_count_values($_SESSION['cart']))
display_cart($_SESSION['cart']);
else
{
echo '<р>Ваша тележка пуста</р>';
echo '<hr />';
}
Из этого кода видно, что если тележка не пуста, вызывается функция display_cart ().
Если же тележка пуста, посетителю просто выводится соответствующее сообщение.
Функция display_cart () всего лишь выводит содержимое тележки в читабельном
HTML-формате, как показано на рис. 27.6 и 27.7. Код функции содержится в библио-
теке output_fns .php и с ним можно ознакомиться в листинге 27.10. Хотя это, в об-
щем-то, функция отображения HTML-содержимого, она достаточно сложна и заслу-
живает отдельного рассмотрения.
Листинг 27.10. Функция display_cart () из библиотеки output_fns .php — эта функция
выводит содержимое покупательской тележки
function display_cart($cart, $change = true, $images = 1)
{
// Выводит элементы, находящиеся в покупательской тележке.
// Дополнительно получает параметр $change, разрешающий (true)
// или запрещающий (false) внесение изменений.
// Дополнительно получает параметр $images, разрешающий (true)
// или запрещающий (false) вывод изображений товаров.
echo '<table border=“0" width="100%” cellspacing="0">
<form action="show_cart.php” method="post“>
<trxth colspan=”'. (l+$images) .'“ bgcolor=''#cccccc'‘>ToBap</th>
<th bgcolor="#cccccc">Цена</th><th bgcolor="#cccccc">Количество</th>
<th bgcolor=''#cccccc">Bcero</th></tr>' ;
// Отобразить каждый элемент в виде строки таблицы
foreach ($cart as $isbn => $qty)
596
Часть V. Реальные проекты на РНР и MySQL
$book = get_book_details($isbn);
echo '<tr>';
if($images ==true)
{
echo '<td align="left">';
if (file_exists("images/?isbn.jpg"))
{
$size = GetlmageSize('images/'.$isbn.'.jpg');
if($size[0]>0 && $size[l]>0)
{
echo '<img src="images/'.$isbn.'.jpg" border=0 ';
echo 'width = '. $size[0]/3 .' height = ' .$size[l]/3 . ’>';
}
}
else
echo ' ';
echo '</td>';
}
echo '<td align="left">';
echo '<a href="show_book.php?isbn=',$isbn..$book['title'].
'</a> by '.$book['author'];
echo '</tdxtd align="center">$'.number_format($book['price'], 2);
echo '</tdxtd align="center">' ;
// Если разрешены изменения, представить количества в текстовых полях ввода
if ($change == true)
echo "<input type='text' name=\"$isbn\" value=\''$qty\" size=\"3\">" ;
else
echo $qty;
echo ’</td><td align=”center">$'.number_format($book['price']*$qty,2).
"</tdx/tr>\n" ;
}
// Вывести строку общего количества и суммы заказа
echo "<tr>
<th colspan=". (2+$images) ." bgcolor=\"#cccccc\"> </td>
<th align = \"center\" bgcolor=\"#cccccc\">
".$_SESSION['items']."
</th>
<th align = center bgcolor=\"#cccccc\">
\$",number_format($_SESSION['total_price'], 2).
'</th>
</tr>';
/I Вывести кнопку сохранения изменений
if($change == true)
{
echo '<tr>
<td colspan="'. (2+$images) .'"> </td>
<td align="center">
<input type= "hidden" name=''save" value="true">
<input type=''image" src="images/save-changes.gif"
border="0" alt="Сохранить изменения">
</td>
Глава 27. Разработка покупательской тележки
597
<td> <Itd>
</tr>';
)
echo ' </formx/table>' ;
2
Давайте рассмотрим базовые алгоритмические конструкции, которые реализует
данная функция:
1. Циклический обход каждого элемента тележки и передача его номера ISBN
в функцию get_book_details (), что позволяет получить итоговую информа-
цию по каждой книге.
2. Для каждой книги выводится изображение, если оно существует. Здесь при
помощи HTML-дескрипторов высоты и ширины изображение немного
уменьшается в размерах. В результате изображения слегка искажаются, однако
они достаточно малы, чтобы это не особенно бросалось в глаза и создавало
проблемы. (Если уж это вас так сильно беспокоит, можно либо изменять
размеры изображений с использованием библиотеки gd, которая рассматри-
валась в главе 21, либо вручную подготовить для каждого продукта изображе-
ния с уменьшенными размерами.)
3. Преобразование каждой записи тележки в ссылку на соответствующую книгу,
то есть на вызов сценария show_book .php с передачей ему в качестве параметра
номера ISBN.
4. Если функция вызывается, когда параметр $chande получает значение true
(либо вообще не получает значения, так как true принимается по умолчанию),
для представления заказзанных количеств выводятся текстовые поля ввода.
Вместе они образуют форму, в которую входит также и кнопка Save Changes.
(Следует отметить, что при повторном вызове функции display_cart() после
осуществления окончательного расчета нельзя допустить, чтобы пользователь
смог еще раз изменить свой заказ.)
Эта функция не содержит ничего особо сложного, тем не менее, выполняет мно-
жество операций. Именно поэтому стоит очень внимательно изучить ее код.
Добавление элементов в тележку
Когда пользователь попадает на страницу show_cart.php в результате щелчка на
кнопке Add То Cart, перед выводом содержимого тележки необходимо выполнить
определенную подготовительную работу. В частности, в тележку следует поместить
соответствующий элемент.
Во-первых, если посетитель пока еще ничего не помещал в тележку, то собственно
тележки и нет, поэтому ее необходимо создать:
if(!isset($_SESSION['cart']))
{
$_SESSION['cart'] = arrayO;
$_SESSION['items'] = 0;
$_SESSION['total_price'] ='0.00';
}
598
Часть V. Реальные проекты на РНР и MySQL
Поначалу тележка пуста.
Во-вторых, когда известно, что тележка создана, в нее можно добавить элемент:
if(isset($_SESSION['cart1][$new]))
$_SESSION['cart'][$new]++;
else
$-SESSION['cart'][$new] = 1;
Здесь мы проверяем, не содержится ли данный товар в тележке. Если это так, ко-
личество данного товара увеличивается на единицу, в противном случае в тележку
добавляется новый элемент.
В-третьих, мы должны определить общую сумму заказа и количество товаров в те-
лежке. Для этого применяются функции calculate_price () и calculate_items ():
$_SESSION['total_price'] = calculate_price($_SESSION['cart']);
$_SESSION['items’] = calculate_items($_SESSION['cart']);
Эти функции содержатся в библиотеке book_fns .php. Их код можно найти, соот-
ветственно, в листингах 27.11 и 27.12.
Листинг 27.11. Функция calculate_price () из библиотеки book_fns .php — эта функция
вычисляет и возвращает общую стоимость содержимого тележки для покупок
function calculate_price($cart)
{
// Вычисляет общую стоимость всех элементов тележки
Sprice = 0.0;
i f(is_array($ cart))
I
$conn = db_connect();
foreach($cart as $isbn => $qty)
{
Squery = "select price from books where isbn='$isbn';
$result = $conn->query(Squery);
if ($result)
{
$item = $result->fetch_object();
$item_price = $item->price;
Sprice +=$item_price*$qty;
}
}
}
return Sprice;
}
Несложно заметить, что функция calculate_price () выполняет поиск в базе
данных цены каждого элемента, помещенного в тележку. Этот процесс требует вре-
мени, поэтому, чтобы не повторять его чаще, нежели это необходимо, цена (равно
как и общее количество элементов) сохраняется в переменных сеанса. Повторные
вычисления выполняются только в случаях, когда содержимое тележки изменяется.
Глава 27. Разработка покупательской тележки
599
Листинг 27.12. Функция calculate„items() из библиотеки book_fns.php —
эта функция вычисляет и возвращает общее количество элементов в тележке
function calculate_iterns($cart)
{
// Подсчитывает общее количество элементов в тележке
Sitems = 0;
if(is_array($cart))
{
$items=array_sum($cart);
}
return $items;
}
Функция calculate_items () намного проще — она лишь суммирует количества
всех элементов тележки с целью получения итогового значения. При этом вызы-
вается функция array_sum(). Если массива еще нет (тележка пуста), функция
calculate_items () возвращаете.
Сохранение изменений содержимого тележки
Когда сценарий show_cart.php вызывается в результате щелчка на кнопке Save
Changes, характер процесса несколько меняется. В данном случае осуществляется
передача данных формы. При внимательном рассмотрении кода можно заметить, что
кнопка Save Changes является кнопкой отправки формы. Эта форма содержит скры-
тую переменную save. Если значение этой переменной установлено, мы знаем, что
сценарий вызван в результате щелчка на кнопке Save Changes. Это означает, что поль-
зователь мог изменить количества элементов, и желает сохранить эти изменения.
Если вернуться к той части кода формы сохранения изменений, можно заметить,
что текстовые поля ввода количества получают имена, совпадающие с номерами
ISBN, которые они представляют:
echo '<input type="text" name="$isbn" value="$qty" size="3">';
Теперь рассмотрим ту часть сценария, которая отвечает за сохранение изменений:
if(isset($_POST['save']))
{
foreach ($_SESSION['cart’] as $isbn => $qty)
{
if($_POST[$isbn]=='0')
unset($_SESSION['cart'][$isbn]);
else
$_SESSION['cart'][$isbn] = $_POST[$isbn];
}
$_SESSION['total_price'] = calculate_price($_SESSION['cart']);
$_SESSION['items'] = calculate_items($_SESSION['cart']);
)
600
Часть V. Реальные проекты на РНР и MySQL
Как видите, выполняется перебор всех элементов, хранимых в тележке, и для ка-
ждого номера ISBN проверяется значение POST-переменной с таким же именем.
В этом случае POST-переменные — это поля ранее рассмотренной формы сохранения
изменений.
Если какое-то поле установлено равным нулю, соответствующий элемент удаляет-
ся из тележки с помощью функции unset (). В противном случае содержимое тележки
обновляется значениями полей формы:
if($_POST[$isbn]=='0')
unset($_SESSION['cart'][$isbn]);
else
$_SESSION['cart'][$isbn] = $_POST[$isbn];
После обновления содержимого тележки повторно вызываются функции calcu-
late_price() и calculate_iterns () для определения новых значений переменных
сеанса total_price и items.
Печать итоговых данных в строке заголовка
Наверняка вы уже заметили, что в строке заголовка каждой страницы сайта ото-
бражаются итоговые данные по состоянию покупательской тележки. Это осуществ-
ляется за счет вывода значений переменных сеанса total_price и items при помощи
функции do_html_header ().
Упомянутые переменные регистрируются, когда пользователь впервые посещает
страницу show_cart .php. Кроме того, мы должны реализовать логику для случаев,
когда пользователь еще ни разу не открывал данную страницу. Эта логика также со-
держится в функции do_html_header ():
if(!$_SESSION(items']) $_SESSION[’items'] = 'O';
if(!$_SESSION['total_price']) $_SESSION[total_price'] ='0.00';
Выполнение окончательного расчета
Когда пользователь щелкает на кнопке перехода к окончательному расчету (Go to
Checkout), вызывается сценарий checkout.php. Доступ к странице окончательного
расчета и связанным с ней страницам должен осуществляться через SSL-соединение,
однако наше демонстрационное приложение этого не требует. (Напоминаем, что
дополнительные сведения о SSL можно найти в главе 17.)
Внешний вид страницы окончательного расчета показан на рис. 27.8.
Данный сценарий требует, чтобы клиент ввел свой почтовый адрес (а также адрес
доставки, если они отличаются). Сценарий достаточно прост, в чем легко убедиться,
просмотрев на код, показанный в листинге 27.13.
Глава 27. Разработка покупательской тележки
601
Рис. 27.8. Сценарий checkout.php принимает детальную ин-
формацию о клиенте
Листинг 27.13. checkout .php — этот сценарий принимает детальную информацию
о клиенте
<?php
// Включить наш набор функций
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
do_html_header(1 Окончательный расчет');
if($_SESSION[1 cart']&&array_count_values($_SESSION['cart’]))
{
display_cart($_SESSION['cart'], false, 0) ;
display_checkout_form();
}
else
echo '<р>Ваша тележка пуста</р>';
display_button(1show_cart.php', 'continue-shopping1, 'Продолжить покупки');
do_html_footer();
Сценарий не содержит ничего особо примечательного. Если тележка пуста, об
этом выводится соответствующее уведомление. В противном случае отображается
форма, показанная на рис. 27.8.
602
Часть V. Реальные проекты на РНР и MySQL
Когда пользователь продолжает работу, щелкнув на кнопке Purchase (Купить) в
нижней части формы, запускается сценарий purchase .php. На рис. 27.9 показан вы-
вод этого сценария.
Рис. 27.9. Сценарий purchase.php вычисляет окончательную
сумму заказа и расходы на доставку, а также принимает данные,
касающиеся платежа
По сравнению с checkout.php этот сценарий немного сложнее. Его код можно
найти в листинге 27.14.
Листинг 27.14. purchase.php — этот сценарий сохраняет заказ в базе данных
и принимает данные, касающиеся платежа
<?php
// Включить наш набор функций
require ('book_sc_fns.php');
// Для покупательской тележки необходимо запустить сеанс
session_start();
do_html_header("Окончательный расчет");
// Создать короткие имена переменных
$name = $_POST['name'];
$address = $_POST[1 address'1 ;
$city = $_POST['city'];
$zip = $_POST['zip'];
$country = $_POST['country'];
Глава 27. Разработка покупательской тележки
603
// Если форма заполнена
if ($_SESSION[' cart' ] &&$name&&$address&&:$city&&$zip&&$country)
{
// Можно ли вставлять в базу данных?
if ( insert_order($_POST)!=false )
(
// Вывести тележку без изображений товаров и не разрешая изменения
display_cart($_SESSION['cart'], false, 0);
display_shipping(calculate_shipping_cost());
// Получить информацию по кредитной карточке
display_card_form($name);
display_button('show_cart.php', 'continue-shopping', 'Продолжить покупки');
}
else
{
echo 'Невозможно сохранить данные. Пожалуйста, повторите попытку позже.';
display_button('checkout.php', 'back', 'Назад');
)
)
else
{
echo 'Вы заполнили не все поля. Пожалуйста, повторите попытку.<hr />';
display_button('checkout.php', ’back', 'Назад');
)
do_html_footer();
Логика сценария довольно-таки проста: выполняется проверка, что клиент запол-
нил все поля формы, после чего в базе данных сохраняется введенная информация
путем вызова функции insert_order (). Это несложная функция, вставляющая сведе-
ния о клиенте в базу данных. Ее код можно найти в листинге 27.15.
Листинг 27.15. Функция insert order () из библиотеки orderfпв. php — эта функция
вставляет в базу данных детальную информацию о заказе
function insert_order($order_details)
{
11 Извлечь детальную информацию о заказе и поместить ее в переменные
extract($order_details);
/I Установить адрес доставки равным почтовому адресу
if(!$ship_name&&!$ship_address&&!$ship_city&&
!$ship_state&&!$ship_zip&&!$ship_country)
{
$ship_name = $name;
$ship_address = $address;
$ship_city = $city;
$ship_state = $state;
$ship_zip = $zip;
$ship_country = $country;
604
Часть V. Реальные проекты на РНР и MySQL
$conn = db_connect();
// Вставка заказа должна выполняться в виде транзакции,
// поэтому необходимо отключить autocommit
$conn->autocommit(FALSE);
// Вставить почтовый адрес клиента
$query = "select customerid from customers where
name = '$name' and address = 'Saddress'
and city = '$city' and state = '$state'
and zip = '$zip' and country = 'Scountry'";
$result = $conn->query(Squery);
if($result->num_rows>0)
{
Scustomer = $result->fetch_object();
Scustomerid = $customer->customerid;
}
else
(
Squery = "insert into customers values
(1', 'Sname','Saddress','$city','Sstate','$zip','Scountry')";
Sresult = $conn->query(Squery);
if (!Sresult)
return false;
}
Scustomerid = $conn->insert_id;
Sdate = date('Y-m-d’);
Squery = "insert into orders values
('', Scustomerid, ".$_SESSION['total_price'].", 'Sdate',
'PARTIAL', '$ship_name',
'$ship_address','$ship_city','$ship_state','$ship_zip',
'$ship_country')";
Sresult = $conn->query(Squery) ;
if (!Sresult)
return false;
Squery = "select orderid from orders where
customerid = Scustomerid and
amount > ".$_SESSION['total_price'].“-.001 and
amount < ".$_SESSION['total-price']."+.001 and
date = 'Sdate' and
order_status = 'PARTIAL' and
ship_name = '$ship_name' and
ship_address = ’$ship_address' and
ship_city = '$ship_city' and
ship_state = ’$ship_state' and
ship_zip = '$ship_zip' and
ship_country = ’$ship_country'";
Sresult = $conn->query(Squery);
if($result->num_rows>0)
(
Sorder = $result->fetch_object();
Sorderid = $order->orderid;
)
Глава 27. Разработка покупательской тележки
605
else
return false;
// Вставить каждую книгу из числа заказанных
foreach($_SESSION['cart'] as $isbn => Squantity)
{
$detail = get_book_details($isbn);
Squery = "delete from order_items where
orderid = 'Sorderid' and isbn = '$isbn'";
$result = $conn->query(Squery) ;
Squery = "insert into order_iterns values
('Sorderid', 'Sisbn', ".Sdetail['price', Squantity)";
Sresult = $conn->query (Squery);
if(!Sresult)
return false;
}
// конец транзакции
$conn->commit();
$conn->autocommit(TRUE);
return Sorderid;
)
Код функции insert_order() достаточно длинный, поскольку необходимо вы-
полнить вставку данных о клиенте, заказе, а также информации о каждой приобре-
таемой книге.
Следует отметить, что различные части вставки оформлены как транзакция, ко-
торая начинается с оператора
$conn->autocommit(FALSE);
и заканчивается операторами
$conn->commit();
$conn->autocommit(TRUE);
Это единственное место в приложении, где должна применяться транзакции. Как
избежать их? Посмотрите на код функции db_connect ():
function db_connect()
{
Sresult = new mysqli('localhost', 'book_sc', 'password', 'book_sc');
if (!Sresult)
return false;
$result->autocommit(TRUE);
return Sresult;
}
Очевидно, что этот код отличается от применяемого в функции insert_order ().
После создания соединения с MySQL включается режим автоматического сохранения
транзакций (autocommit). Это гарантирует автоматическое сохранение результатов
выполнения любого SQL-оператора. Однако если необходимо выдать целую последо-
вательность взаимосвязанных SQL-операторов в виде единственной транзакции, по-
требуется отключить режим autocommit, выполнить серию вставок, сохранить дан-
ные и вновь включить режим autocommit.
606
Часть V. Реальные проекты на РНР и MySQL
Затем определяется стоимость доставки по адресу клиента, которая выводится на
экран с помощью следующей строки кода:
display_shipping(calculate_shipping_cost());
Код вычисления стоимости доставки, оформленный в виде функции calcu-
late_shipping_cost (), всегда возвращает значение $20. В реальном сайте, реали-
зующем онлайновую торговлю, потребуется предоставить клиенту выбор способа
доставки, подсчитать затраты на доставку по различным адресам и соответствующим
образом вычислить стоимость доставки.
Далее отображается форма для ввода клиентом данных о кредитной карточке с
помощью функции display_card_form () из библиотеки output_fns .php.
Реализация платежа
Когда клиент щелкает на кнопке Purchase, с помощью сценария process.php об-
рабатываются данные, касающиеся платежа. Результаты успешного выполнения пла-
тежной операции показаны на рис. 27.10.
Код сценария process .php можно найти в листинге 27.16.
Рис. 27.10. Транзакция успешно завершена, и товар будет дос-
тавлен по указанному адресу
Листинг 27.16. process .php — этот сценарий обрабатывает платеж и выводит
его результаты
<?php
// Включить наш набор функций
require ('book_sc_fns.php1);
Глава 27. Разработка покупательской тележки
607
// Для покупательской тележки необходимо запустить сеанс
session_start();
do_html_header('Окончательный расчет');
// Создать короткие имена переменных
$card_type = $_POST['card_type’];
$card_number = $_POST[1card_number'];
$card_month = $_POST['card_month'];
$card_year = $_POST['card_year'];
$card_name = $_POST[1card_name'];
if($_SESSION['cart’J&&$card_type&&$card_number&&
$card_month&&$card_year&&$card_nanie )
{
// Вывести тележку без изображений товара и не разрешая изменения
display_cart($_SESSION['cart1], false, 0);
display_shipping(calculate_shipping_cost());
if(process_card($_POST))
{
// Очистить покупательскую тележку
session_destroy();
echo 'Спасибо за то, что воспользовались нашим сайтом для совершения
покупок. Ваш заказ размещен.1;
display_button(' index, php' , ' continue-shopping' , 'Продолжить ппктп.ки ' ) ;
}
else
{
echo 'Невозможно обработать вашу кредитную карточку.';
echo 'Пожалуйста, свяжитесь с выдавшей ее организацией либо
повторите ввод.';
display_button('purchase.php', 'back', 'Назад');
}
}
else
{
echo 'Вы заполнили не все поля. Пожалуйста, повторите попытку.<hr >';
display_button('purchase.php', 'back', 'Назад');
)
do_html_footer();
Мы обрабатываем данные по кредитной карточке, предоставленные клиентом, и в
случае, если все завершается успешно, уничтожаем сеанс клиента.
В нашем упрощенном примере функция обработки данных кредитной карточки
просто возвращает значение true. Если быть точнее, сначала необходимо преду-
смотреть набор проверок на допустимость (в том числе, проверку, не истек ли срок
действия кредитной карточки, а также корректность введенного номера карточки), и
только затем переходить к собственно совершению платежа.
В реальном сайте потребуется принять решение, какой механизм транзакций бу-
дет использоваться.
608
Часть V. Реальные проекты на РНР и MySQL
Существуют следующие возможности:
Заключить договор с поставщиком расчетных (клиринговых) транзакций.
Здесь имеется множество альтернатив, зависящих от региона, в котором вы
проживаете. Некоторые поставщики предлагают клиринговые услуги в реаль-
ном времени. Необходимость в таких операциях зависит от услуг, предлагае-
мых вашим сайтом. Если вы осуществляете обслуживание в онлайновом режи-
ме, то, скорее всего, возможность совершения платежей в реальном времени
вам понадобится. В том случае, если вы обеспечиваете только продажу и дос-
тавку некоторых товаров, это менее важно. В любом случае, поставщики кли-
ринговых транзакций освобождают вас от довольно-таки неприятной ответст-
венности за хранение номеров кредитных карточек.
Отправлять номера кредитных карточек себе самому в зашифрованных сооб-
щениях электронной почты, например, с использованием PGP или GPG, как
упоминалось в главе 17. После получения и дешифрации таких сообщений
транзакции можно обрабатывать вручную.
Хранить номера кредитных карточек в своей базе данных. Мы настоятельно не
рекомендуем прибегать к этому методу, если вы не уверены, что должным об-
разом обеспечили высочайшую степень защищенности своей системы. Допол-
нительную информацию об этой не слишком хорошей идее можно почерпнуть
в главе 17.
На этом обзор модулей, реализующих концепции покупательской тележки и пла-
тежей, завершен.
Реализация интерфейса администрирования
Планируемая в этом проекте реализация интерфейса администрирования доста-
точно проста. Она сводится к построению Web-интерфейса взаимодействия с базой
данных, в котором применяется входная аутентификация. По большому счету, в ос-
новном применяется код, написанный в главе 26. Для полноты картины мы включили
этот код и в данную главу, снабдив его попутно небольшими комментариями.
Интерфейс администрирования требует, чтобы пользователь входил в систему
через сценарий login.php, который будет выводить меню администрирования с по-
мощью сценария admin.php. Страница входа в систему показана на рис. 27.11. (Для
краткости мы опустили файл login.php, поскольку он почти полностью повторяет
одноименный файл из главы 26. Кроме того, сценарий login.php можно найти на
прилагаемом к книге компакт-диске.) Внешний вид меню администрирования пока-
зан на рис. 27.12.
Код реализации меню администрирования представлен в листинге 27.17.
Глава 27. Разработка покупательской тележки
609
Адмиихх грирование - Mozffla {ВиМ Й>! 20041 rzxia)
: Файл Граека Вид П^зетод Закладки Инструменты Окна Франка Oebyg QA
'З®- Ж i http://localhostiphpmysql3e/chaptsr27/fogin.p}p
Назад с ’ « Обновить С '
Вмк-O-Rama
Всегокшг^О
Общая сумма= J0.OO
Администриро в ание
Имя пользователя:
Пароль- р—
Войти £,
Рис. 27.11. Для доступа к функциям администрирования поль-
зователь должен пройти через страницу входа в система
Рис. 27.12. Меню администрирования предоставляет доступ к
набору функций администрирования
610
Часть V. Реальные проекты на РНР и MySQL
Листинг 27.17. admin.php — этот сценарий выполняет аутентификацию
администратора и предоставляет ему доступ к функциям администрирования
<?php
// Включить библиотеки функций для этого приложения
require_once('book_sc_fns.php');
session_start();
if ($_POST['username'1 && $_POST['passwd'])
// Пользователь только что попытался войти в систему
{
$username = $_POST['username1];
$passwd = $_POST['passwd'];
if (login($username, $passwd))
{
//Если пользователь записан в базе данных, зарегистрировать его идентификатор
$_SESSION['admin_user’] = $username;
}
else
(
// Неудачный вход в систему
do_html_header('Проблема: ') ;
echo 1 Вход в систему невозможен.
Для просмотра этой страница необходимо войти в систему.';
do_html_url('login.php', 'Вход');
do_html_footer();
exit;
}
}
do_html_header('Администрирование');
if (check_admin_user())
display_adminjnenu () ;
else
echo 'У вас нет прав для доступа на страницу администрирования.';
do_html_footer();
Этот код сильно напоминает один из сценариев в главе 26. Как только администра-
тор достигает этой точки, он может изменять свой пароль либо выходить из системы —
данный код идентичен таковому из главы 26, поэтому здесь он не рассматривается.
Идентификация пользователя-администратора после входа в систему осуществля-
ется через переменную сеанса admin_user и функцию check_admin_user (). Эта и
другие функции, используемые сценариями администрирования, содержатся в биб-
лиотеке admin_fns .php.
Когда администратору требуется добавить новую категорию или книгу, вызывает-
ся либо сценарий insert_category_form. php, либо сценарий insert_book_f orm. php.
Каждый сценарий предоставляет администратору форму для заполнения. Фор-
мы обрабатываются соответствующими сценариями (insert_category.php и
insert_book.php), которые проверяют форму на предмет заполнения и сохраняют
Глава 27. Разработка покупательской тележки
611
новую информацию в базе данных. Давайте рассмотрим только сценарии, связанные
с добавлением книги, поскольку обе пары сценариев отличаются друг от друга лишь
незначительно.
Вывод сценария insert._book_form.php показан на рис. 27.13.
.JoixJ
й Добавление новой. книги - Могйа {BuiM И>:>лНОЛЙЯ$]
3
Обновить
;.:;;файя Гравка Вид П§®ход Закладки 4 инструменты Справка Debug QA
и 4 ....
: Назад
Дрвавитькниг^ |
Назад в меню администрирования
' Готово
Рис. 27.13. Эта форма позволяет администратору добавлять в
онлайновый каталог новые книги
Вы наверняка заметили, что поле Категория представляет собой HTML-элемент
SELECT. Опции для этого элемента получаются в результате вызова ранее рассмот-
ренной функции get_categories ().
В результате щелчка на кнопке Добавить книгу запускается сценарий
insert_book. php, код которого показан в листинге 27.18.
Листинг 27.18. insertjbook.php — этот сценарий проверяет допустимость данных,
введенных для новой книги, и помещает их в базу данных
<?php
// Включить библиотеки функций для этого приложения
require_once(’book_sc_fns.php1);
session_start();
do_html_header(1 Добавление книги’);
if (check_admin_user())
{
if (filled_out($_POST))
{
$isbn = $_POST[’isbn'];
$title = $_POST['title'];
$author = $_POST[1 author1];
$catid = $_POST('catid'];
$price = $_POST['price'];
$description = $_POST['description'];
612
Часть V. Реальные проекты на РНР и MySQL
if(insert_book($isbn, $title, $author, $catid, $price, $description))
echo "Книга 1 ". stripslashes($title)' добавлена в базу данных.<br />";
else
echo "Книга stripslashes($title).
"' не может быть добавлена в базу данных.<br />";
}
else
echo 'Вы заполнили не все поля формы. Пожалуйста, повторите попытку.';
do_html_url('admin.php', 'Назад в меню администрирования');
}
else
echo 'У вас нет прав для доступа на страницу администрирования.';
do_html_footer();
?>
Легко заметить, что этот сценарий вызывает функцию insert_book(). Эта и дру-
гие функции, используемые сценариями администрирования, содержатся в библио-
теке admin_fns.php.
Помимо добавления новых категорий и книг, пользователь с правами администра-
тора может редактировать и удалять эти элементы. При реализации упомянутой функ-
циональности мы постарались повторно использовать максимально возможный объем
кода. Когда администратор выполняет щелчок на ссылке Перейти на основной сайт в
меню администрирования, выводится индекс категорий (с помощью рассмотренного
ранее сценария index.php). После этого администратор может выполнять навигацию по
сайту так же, как и рядовой посетитель. При этом задействуются те же самые сценарии.
Однако с навигацией администратора связаны некоторые отличия. Для него вы-
водятся особые опции, связанные с тем, что зарегистрирована переменная сеанса
admin_user. Например, на рассмотренной ранее странице show_book.php будут за-
метны отличия в меню опций, что можно видеть на рис. 27.14.
Рис. 27.14. Вывод сценария show_book. php для админи-
стратора отличается от вывода для рядового посетителя
Глава 27. Разработка покупательской тележки
613
На этой странице администратору предоставляются две дополнительные опции:
Edit Item (Редактировать элемент) и Admin Menu (Меню администрирования). Кроме
того, в правом верхнем углу вместо кнопки вызова покупательской тележки находит-
ся кнопка Log Out (Выход).
Все это реализуется с помощью следующего фрагмента кода, который взят из лис-
тинга 27.8:
if( check_admin_user() )
{
display_button("edit_book_form.php?isbn=$isbn", ’edit-item’,
'Редактировать элемент’);
display_button('admin.php', 'admin-menu', 'Меню администрирования');
display_button($target, 'continue', 'Продолжить');
}
Если вновь вернуться к сценарию show_cat. php, можно заметить, что в нем также
присутствуют данные опции.
Когда администратор выполняет щелчок на кнопке Edit Item, запускается сцена-
рий edit_book_form.php. Вывод этого сценария показан на рис. 27.15.
Фактически это та же форма, что ранее использовалась для извлечения данных,
связанных с книгой. Просто мы встроили в нее возможность передачи и отображе-
ния существующих сведений о книге. То же самое было выполнено и в отношении
формы просмотра категорий. Все только что сказанное отражено в листинге 27.19.
Рис. 27.15. Сценарий edit_book_form.php дает администратору
возможность редактировать информацию о книге и удалять книгу
614
Часть V. Реальные проекты на РНР и MySQL
Листинг 27.19. Функция display_book_form() из библиотеки admin_fns.php —
эта функция имеет двойное назначение, выводя формы вставки
и редактирования информации о книге
function display_book_form($book = 11)
II Отображает форму для книги.
// Эта форма во многом подобна форме для категорий.
// Форма может применяться для вставки и редактирования информации о книге.
// Для вставки передавать параметр не нужно. В результате Sedit
// получит значение false и форма вызовет сценарий insert_book.php.
II Для обновления данных следует передать массив, содержащий данные о книге.
// Форма отобразит предыдущие данные и кнопку, приводящую к вызову update_book.php.
// Кроме того, добавляется кнопка удаления книги.
{
// Если передается существующая книга, перейти в "режим редактирования"
Sedit = is_array($book);
// Большая часть формы представляет собой простой HTML-код
//с небольшими вставками РНР-кода.
?>
<form method='post1
action="<?php echo $edit?'edit_book.php':'insert_book.php';?>">
ctable border-"0">
<tr>
<td>ISBN:</td>
ctdxinput type='text' name='isbn'
value="c?php echo $edit?$book['isbn']:’'; ?>"></td>
<td>Название:</td>
ctdxinput type='text' name='title'
value="c?php echo $edit?$book['title1]:''; ?>*>c/td>
ctd>ABTop:</td>
ctdxinput type='text' name='author'
value="c?php echo $edit?$book['author']:''; ?>">c/td>
ctd>Категория:c/td>
ctdxselect name=' catid' >
c?php
// Прочитать из базы данных список возможных категорий
$cat_array=get_categories();
foreach ($cat_array as $thiscat)
{
echo ’coption value="';
echo $thiscat['catid'] ,-
echo '”';
// Если книга существует, поместить ее в текущую категорию
if ($edit && Sthiscat['catid'] == $book[’catid'])
echo ' selected';
echo '>';
echo $thiscat[1 catname'];
Глава 27. Разработка покупательской тележки
615
echo "c/option>\n";
}
</select>
c/td>
</tr>
ctr>
ctd>UeHa:c/td>
ctdxinput type='text' name='price'
value=”<?php echo $edit?$book['price'; ?>"></td>
</tr>
<tr>
c tds-Описание:</td>
<tdxtextarea rows='3' cols='50'
name=' descriptions
<?php echo $edit?$book['description; ?>
c / textar eax / td>
</tr>
<tr>
<td <?php if (!$edit) echo 'colspan=\’2\''; ?> align="center">
<?php
if ($edit)
// Если был обновлен номер ISBN, для поиска книги
//в базе данных понадобится старый номер ISBN
echo 'cinput type="hidden" name="oldisbn"
value="'.$book['isbn
?>
cinput type='submit'
value="c?php echo $edit?'Обновить':'Добавить'; ?> книгу">
c/formx/td>
c? php
if (Sedit)
{
echo 'ctd>';
echo 'cform method="post" action="delete_book.php">';
echo 'cinput type="hidden" name="isbn"
value="'.$book['isbn
echo 'cinput type="submit"
value="Удалить книгу">';
echo 'c/form>c/td>';
)
c/td>
c/tr>
c/table>
c/form>
c?php
)
Если передается массив, содержащий данные о книге, форма переводится в режим
редактирования, а поля заполняются существующими данными:
cinput type='text' name='price'
value="c?php echo $edit?$book['price; ?>">
616
Часть V. Реальные проекты на РНР и MySQL
При этом используется даже другая кнопка отправки формы. Фактически, на
форме редактирования их две — одна для обновления информации о книге, а вто-
рая— для удаления книги целиком. С этими кнопками связаны вызовы сценариев
edit—book.php и delete_book.php, которые соответствующим образом обновляют
базу данных.
Версии рассмотренных выше сценариев, связанные с категориями, работают в
основном идентично, за исключением одного момента. Когда администратор пыта-
ется удалить категорию, этого не произойдет, пока категория содержит книги. (Это
проверяется путем отправки запроса в базу данных.) В таком случае минимизируется
вероятность ошибочного удаления, как упоминалось в главе 8. В нашей си туации, ес-
ли удалить категорию, которая содержит книги, эти книги станут висячими. Переход
к ним будет невозможен, поскольку с данными книгами не связана ни одна категория.
На этом обзор интерфейса администрирования завершается. За дополнительны-
ми сведениями обращайтесь к коду, который содержится на прилагаемом к книге
компакт-диске.
Расширение проекта
Если вы четко следовали всем приведенным выше инструкциям, то сейчас распо-
лагаете достаточно простой системой покупательской тележки. Разумеется, сущест-
вуют многочисленные расширения и усовершенствования проекта, проделав кото-
рые можно существенно улучшить приложение в целом:
В реальном онлайновом магазине должна быть предусмотрена система отсле-
живания и выполнения заказов. В данный момент мы не имеем какой-либо
возможности просматривать размещенные заказы.
Клиентам должна быть доступна возможность проверять обработку своих зака-
зов без необходимости связываться с владельцем магазина. Мы считаем важным,
что посетителю для ознакомления с предложением товара не приходится вхо-
дить в систему. Тем не менее, реализация для существующих клиентов некоторо-
го метода аутентификации даст им возможность просматривать ранее разме-
щенные заказы, а администратору — объединят!, стили поведения в профили.
В настоящее время необходимо пересылать по FTP изображения обложек книг
в каталог изображений и присваивать им надлежащие имена. Дабы хоть немно-
го упростит!, этот процесс, можно добавить на страницу вставки книг функцию
загрузки файла.
Можно добавить механизм входа пользователей, персонализацию, систему вы-
дачи рекомендаций по книгам, онлайновые обзоры, сопутствующие програм-
мы, проверку складских запасов и многое другое. Возможности практически не
ограничены.
Использование существующей системы
Если вы хотите получить приложение покупательской тележки с богатыми функ-
циональными возможностями и высоким быстродействием, возможно, стоит вос-
пользоваться какой-нибудь существующей системой. Одна из широко известных сис-
Глава 27. Рвзработка покупательской тележки
617
тем тележек с открытым исходным кодом, реализованная на языке РНР, носит назва-
ние FishCartSQL и доступна для свободной загрузки по адресу:
http://www.fishcart.org/
Эта система располагает множеством расширенных функций, к числу которых от-
носятся: отслеживание клиентов, ограниченные по времени продажи, поддержка не-
скольких языков, обработка кредитных карточек и поддержка нескольких онлайновых
магазинов на одном сервере. Естественно, в существующей системе всегда что-то может
показаться излишним, а чего-нибудь может не хватать. Преимущество продукта с от-
крытым исходным кодом состоит в возможности вносить любые изменения в код.
Что дальше
В следующей главе будут рассматриваться вопросы построения онлайновой сис-
темы управления содержимым, которая пригодна для управления цифровой интел-
лектуальной собственностью. Это может оказаться исключительно полезным для
сайта, основанного на содержимом.
618
Часть V. Реальные проекты на РНР и MySQL
28
Разработка системы
управления содержимым
Ватой главе мы рассмотрим принципы разработки системы управления содер-
жимым (content management system — CMS) для хранения, индексации и поиска
текста, а также мультимедиа-данных.
Системы управления содержимым исключительно полезны для Web-сайтов, на ко-
торых содержимое поддерживается более чем одним автором, сопровождение вы-
полняет нетехнический персонал, либо содержимое и графическое оформление раз-
рабатываются различными людьми или даже отделами.
В этой главе мы создадим приложение, которое поможет авторизованным пользо-
вателям управлять цифровой интеллектуальной собственностью организации.
В главе будут рассмотрены следующие аспекты:
Представление Web-страниц с использованием набора шаблонов.
Построение поискового механизма, который индексирует документы в соот-
ветствии с метаданными.
Задача
Давайте предположим, что в группу Web-разработчиков компании “Сетевые Но-
вости Очень Быстро, Inc.” (“SuperFastOnlineNews") входят талантливые дизайнеры и
несколько авторов-лауреатов. Сайт содержит регулярно обновляемые страницы но-
востей, спорта и погоды. Главная страница отображает новейшие анонсы по каждой
из трех категорий страниц.
В компании “Сетевые Новости Очень Быстро” дизайнеры обеспечивают привле-
кательный вид содержимого. Это как раз то, что у них получается исключительно
хорошо. С другой стороны, авторы пишут замечательные статьи, но не умеют толком
рисовать и создавать Web-сайты.
Наша задача состоит в том, чтобы позволить каждому член}' команды сконцентри-
роваться на своей работе и объединить результаты усилий, дабы получилась сверх-
оперативная служба новостей, подобающая названию компании.
Требования к проекту
В рамках данного проекты мы должны создать систему, которая:
Увеличит продуктивность работы, позволив авторам сконцентрироваться на
статьях, а дизайнерам — на оформлении.
Позволит редактору просматривать статьи и выбирать из них те, которые бу-
дут публиковаться.
Создаст единообразный внешний вид сайта с использованием шаблонов страниц.
Предоставит авторам доступ только к предназначенным для них областям сайта.
Позволит легко изменять оформление любого раздела либо даже всего сайта.
Предотвратит изменение актуального содержимого.
Существующие системы
Существует великое множество систем управления содержимым, как бесплатных,
так и коммерческих. Перед тем как разрабатывать собственную систему- неплохо оз-
накомиться с несколькими доступными системами. Как и при выполнении других
проектов, важно найти компромисс между использованием системы, разработанной
кем-то, и созданием совершенно новой системы.
Написание собственной системы CMS предоставляет высокую степень гибкости,
однако требует выполнения большого объема работ. Следует принять решения отно-
сительно того, как вывод системы CMS будет интегрирован в Web-сайт, а также как
будет поддерживаться динамическое содержимое.
Существующие системы CMS могут предоставить очень развитые функции при
относительно небольшом объеме работ. Как правило, они генерируют очень гибкий
вывод, поскольку это является одной из главных целей таких систем, однако они час-
то привязывают вас к определенному рабочему потоку и могут не поддерживать ди-
намическое содержимое.
Разработка простой системы CMS с возможностями, описанными в этой главе,
длится недолго, но чем больше возможностей требуется, тем более сложной стано-
вится задача и тем больше смысла приобретет адаптация какой-то существующей сис-
темы.
Редактирование содержимого
Первым делом, необходимо продумать способ ввода содержимого в систему, а
также методы его хранения и редактирования.
Ввод содержимого в систему
Мы должны определиться с методом передачи компонентов статей и оформле-
ния. Для этих целей существует множество возможностей, три из которых будут рас-
смотрены в следующих разделах.
620
Часть V. Реальные проекты на РНР и MySQL
FTP/SCP
Авторам и дизайнерам можно предоставить FTP- или SCP-доступ к определенным
областям Web-сервера. Это позволит им загружать на сервер файлы со своих локальных
компьютеров. Для загружаемых файлов потребуется выработать строгий стандарт
именования (который позволит четко идентифицировать принадлежность изображе-
ний к тем или иным статьям). С другой стороны, можно воспользоваться основанной
на Web системой, которая будет решать упомянутые задачи отдельно от загрузки фай-
лов через FTP.
К сожалению, выдача полномочий различным пользователям при этом достаточ-
но сложна, поэтому применять FTP/SCP в данном примере мы не будем.
Метод загрузки файлов
Как упоминалось в главе 18. протокол HTTP предоставляет метод загрузки файлов
с помощью Web-браузера. Язык РНР позволяет решать эту задачу очень просто и эф-
фективно.
Кроме того, метод загрузки файлов даег возможность обрабатывать содержимое
каким угодно способом. Вы можете хранить текст в базе данных, а не в файлах. Меха-
низм загрузки создает временный файл. Для сохранения информации в файле этот
временный файл копируется на постоянное место. Для сохранения информации в
базе данных содержимое временного файла читается и записывается в базу. В этом
проекте будет реализована необязательная загрузка файлов для статей и обязатель-
ная — для иллюстраций.
Интерактивное редактирование
Пользователи должны иметь возможность создавать и редактировать документы
без активизации FTP, SCP или метода загрузки файлов. Вместо этого авторам, на-
пример, можно предоставить в окне большое текстовое поле, в котором они смогут
редактировать содержимое своих статей.
Несмотря на относительную простоту этого метода, он часто оказывается очень
эффективным. Web-браузер не предоставляет каких-либо возможностей по редакти-
рованию текста, кроме лишь функций копирования и вставки, за реализацию кото-
рых отвечает операционная система. Однако когда требуется внести лишь небольшие
изменения, скажем, исправить орфографическую ошибку, посредством этих функций
подобные действия осуществляются достаточно быстро. К сожалению, поскольку
HTML-элемент textarea не поддерживает многие усовершенствованные функции,
например, проверку орфографии, вам потребуется это делать самостоятельно.
Как и при методе загрузки файлов, данные формы можно записать в файл либо
сохранить в базе данных.
Преимущество хранения содержимого
в базах данных перед файлами
На начальном этапе необходимо принять чрезвычайно важное решение относи-
тельно метода хранения содержимого после его загрузки в систему.
Поскольку вместе с текстом хранятся и метаданные, мы приняли решение помес-
тить текстовую часть содержимого в базу данных. Несмотря на то что в базах данных
Глава 28. Разработка системы управления содержимым
621
MySQL можно хранить мультимедиа-данные, в общем случае предпочтительнее дер-
жать загружаемые изображения в файловой системе. Как обсуждалось во второй час-
ти книги, использование больших двоичных объектов (BLOB) в базе данных MySQL
может привести к снижению производительности.
В базе данных будут храниться лишь имена файлов изображений. HTML-
дескриптор <IMG> может напрямую ссылаться на графический файл, как обычно.
Структура документов
В качестве примера статьи будет использоваться краткий текст новостей, вклю-
чающий один-два абзаца и единственное изображение. Совершенно очевидно, что
такая статья ориентируется, прежде всего, на тех, у кого мало времени. Такого рода
документы, включающие заголовок, парочку абзацев и какую-то иллюстрацию, впол-
не можно считать структурированными.
Чем выше степень структуризации документа, тем проще его разбить на состав-
ляющие, которые будут храниться в базе данных. Преимущество такого подхода со-
стоит в возможности единообразного структурированного представления всех доку-
ментов.
В качестве примера возьмем статью новостей. Заголовок будет храниться в своем
поле отдельно от текста. Изображение, по самой своей природе, является отдельным
компонентом документа.
Поскольку заголовок является отдельным элементом, для его отображения можно
задать стандартный шрифт и стиль, а также легко отделить заголовок от остальной
части статьи, сформировав главную страницу заголовков.
Другой подход применительно к крупным документам предполагает организацию
отдельных компонентов документа в соответствие с отношением “один ко многим”.
Другими словами, каждый абзац, заголовок или изображение будет храниться в от-
дельной строке базы данных и иметь связь с идентификатором главного документа.
Такой вид динамической структуры документа обеспечить большую гибкость при об-
работке и визуализации структуры документов.
Использование метаданных
Для целей этого проекта мы уже решили, что запись для каждой статьи должна со-
держать заголовок, текст и изображение. Тем не менее, ничто не мешает нам хранить
в той же записи и другие данные.
Наша система будет автоматически вставлять значения, описывающие автора ста-
тьи и время ее последней модификации. Эти значения могут автоматически отобра-
жаться в нижней части статьи и служить подписью и датой написания статьи, тем са-
мым избавляя автора от необходимости ручного добавления упомянутой информации.
Кроме того, иногда полезно добавлять неотображаемые данные, которые называ-
ются метаданными. Хорошим примером может послужить хранение ключевых слов,
которые будут использоваться в качестве индексов в поисковом механизме.
Вместо того чтобы сканировать весь текст каждой статьи, поисковый механизм бу-
дет извлекать ключевые слова для каждой статьи и на их основе определять соответст-
вие критериям поиска. В результате администратор сайта сможет получить полный
контроль над соответствием ключевых слов и фраз определенным документам.
622
Часть V. Реальные проекты на РНР и MySQL
В нашем примере мы разрешаем связывать со статьей любое количество ключе-
вых слов, а также присваивать каждому ключевому слову некоторый “весовой” коэф-
фициент, определяющий степень значимости ключевого слова. Значения весовых
коэффициентов должны лежать в диапазоне от 1 до 10.
Затем можно разработать алгоритм функционирования поискового механизма,
который будет располагать статьи, соответствующие критериям поиска, согласно
человеческой шкале значимости. Такой подход позволит отказаться от реализации
сложного алгоритма, который будет интерпретировать текст статей и принимать
решение на основе своего достаточно ограниченного “понимания”, а также некото-
рого набора жестко фиксированных правил.
Если хранимые данные содержат один лишь текст, то лучшее решение обеспечит
встроенные в MySQL функции полнотекстовой индексации и поиска. Тем не менее,
разработанный нами поисковый механизм, пусть и проще, но может работать с доку-
ментами различных типов и мультимедийным содержимым.
Не стоить и говорить, что метаданные должны храниться в базе данных. Ничто не
мешает нам пользоваться HTML-дескриптором <МЕТА> либо даже применять XML для
создания документов. Однако лучше при малейшей возможности воспользоваться
преимуществами базы данных, которая позволит эффективно отслеживать все про-
сматриваемые документы.
Форматирование вывода
В нашем примере сайта новостей страницы отображаются в простом, однако
структурированном формате. Каждая страница содержит набор статей, сформатиро-
ванных одним и тем же образом. Прежде всего, заголовок выводится крупным шриф-
том, слева внизу отображается фотография, а справа — собственно текст статьи.
Страница целиком содержится в шаблоне стандартной страницы, что является пред-
посылкой непротиворечивого оформления всего сайта.
На рис. 28.1 показана логическая структура страниц, используемая в нашем при-
мере.
ОБЛАСТЬ ЗАГОЛОВКА
БОКОВОЕ МЕНЮ МЕСТО основного СОДЕРЖИМОГО
НИЖНИЙ КОЛОНТИТУЛ
Рис. 28.1. Логическая структура страницы
следует общим соглашениям и легко воспро-
изводится средствами HTML
Глава 28. Разработка системы управления содержимым
623
Реализация структуры шаблонов, подобной той, что используется для оформле-
ния страниц, достаточно проста. Вы просто разбиваете страницу на четыре части:
заголовок, боковое меню, нижний колонтитул, который не изменяется, и содержи-
мое страницы, меняющееся как с течением времени, так и от страницы к странице.
Когда бы ни выводилась страница, сначала отображается заголовок и боковое меню,
затем содержимое и, наконец, нижний колонтитул.
Заголовок и боковое меню выводится с помощью файла header. php, а нижний ко-
лонтитул — с помощью файла footer.php. Основное содержимое каждой страницы
генерируется соответствующим сценарием.
Реализация сайта с шаблонами заголовка и нижнего колонтитула позволяет легко
и просто изменять оформление сайта, внося требуемые модификации только в фай-
лы шаблонов.
Обзор решения
Перечень файлов данного приложения приведен в табл. 28.1. Таблица 28.1. Файлы приложения управления содержимым
Имя Tun Описание
create_database.sql SQL SQL-запрос для создания базы данных и записи тестовых данных.
include_fns.php Функции Набор подключаемых файлов для административной части приложения.
db_fns.php Функции Набор функций д ля подключения к базе данных содержимого.
select_fns.php Функции Набор вспомогательных функций д ля создания выпадающих списков <select> на основе базы данных.
user_auth_fns.php Функции Набор функций для аутентификации пользователей- администраторов.
header.php i Шаблон Отображает заголовок в верхней части каждой страницы со- держимого.
footer .php Шаблон Отображает нижний колонтитул в нижней части кажцой страт ницы содержимого.
logo.gif Изображение Файл логотипа, выводимого сценарием header .php.
index.php Приложение Отображает самый новый заголовок каждой страницы сайта.
admin/index.php Приложение Реализует меню функций администрирования.
page.php Приложение Выводит список заголовков и текст для определенной страницы.
resize_image.php Приложение Изменяет на лету размеры изображений для сценария index.php.
search_form.php Приложение Форма ввода ключевых слов для выполнения поиска в содержимом сайта.
search.php Приложение Отображает заголовки содержимого, соответствующего ключевым словам.
login.php Приложение Сценарий, выполняющий аутентификацию пользователя и вход его в систему.
624
Часть V. Реальные проекты на РНР и MySQL
Окончание табл. 28.1
Имя Tun Описание
logout.php Приложение Сценарий, осуществляющий выход пользователя из сис- темы.
writer.php Приложение Перечисляет статьи, написанные вошедшим в систему пользователем. Пользователю доступны опции добавле- ния, редактирования и удаления статей.
story.php Приложение Окно информации о статьях для редактирования или добавления новых статей.
s tory_submi t.php Приложение Сценарий, который добавляет новую статью либо переда- ет изменения на основе данных, введенных в story. php.
delete_story.php Приложение Сценарий, который обрабатывает запрос на удаление статьи, переданный из сценария stories. php.
keywords.php Приложение Список ключевых слов для статьи с возможностью их добавления либо удаления.
keyword_add.php Приложение Сценарий, который обрабатывает запрос на добавление ключевого слова, переданный из сценария keywords. php.
keyword_de1et e.php Приложение Сценарий, который обрабатывает запрос на удаление ключевого слова, переданный из сценария keywords. php.
publish.php Приложение Редакторский список статей с указанием, какие из них опубликованы, а также с возможностью переключения состояния каждой из них.
publish_story.php Приложение Сценарий, который обрабатывает запрос на публика- цию, переданный из сценария publ i sh. php. ,
unpubl i sh_s t о ry. php Приложение Сценарий, который обрабатывает запрос на отмену пуб- ликации, переданный из сценария publish.php.
Проектирование базы данных
В листинге 28.1 показаны SQL-запросы, используемые для создания 0азы дан-
ных системы управления содержимым. Этот листинг представляет часр_. файла
create_database. sql. Файл на прилагаемом к книге компакт-диске, помимо этого,
содержит запросы для заполнения базы данных тестовыми примерами пользователей
и статей.
Листинг 28.1. Фрагмент create_database. sql — SQL-код для создания базы данных
системы управления содержимым
drop database if exists content;
create database content;
use content;
drop table if exists writers;
create table writers (
username varchar(16) not null primary key,
password char(40) not null,
full_name text
) ;
Глава 28. Разработка системы управления содержимым
625
drop table if exists stories;
create table stories (
id int not null primary key auto. .increment.
writer varchar(16) not null. # внешний ключ writers.username
page varchar(16) not null, headline text, story_text text, picture text, created int, modified int, published int ) ; drop table if exists pages; create table pages ( code varchar(16) primary key, description text ); drop table if exists writer_permissions; create table writer_permissions ( # внешний ключ pages.code
writer varchar(16) not null. # внешний ключ writers.username
page varchar(16) not null, primary key(writer, page) ) ; drop table if exists keywords; create table keywords ( # внешний ключ pages.code
story int not null, keyword varchar(32) not null, weight int not null, primary key(story, keyword) # внешний ключ stories.id
grant select, insert, update, delete
on content.*
to content@localhost identified by 'password';
Мы должны хранить краткие сведения о каждом авторе, включая входное имя и
пароль, в таблице writers. Полные имена и фамилии авторов будут храниться с це-
лью дальнейшего вывода после каждой статьи, а также для приветствия самих авто-
ров после их входа в систему.
Таблица pages содержит заголовки каждой страницы, на которой будут отобра-
жаться статьи. Таблица writer_permissions реализует отношение “многие ко мно-
гим”, которое отражает, на какие страницы автор может посылать свои статьи.
Таблица stories содержит отдельные поля для компонентов headline (заголо-
вок), story_text (текст статьи) и picture (изображение), речь о которых шла выше.
Поля created (создана), modified (модифицирована) и published (опубликована)
имеют целочисленный тип и хранят метки времени Unix соответствующих событий.
Для создания базы данных потребуется выполнить следующую команду:
mysql —u root < create_database.sql
626
Часть V. Реальные проекты на РНР и MySQL
Обратите внимание, что если база данных с именем content существовала ранее,
она будет удалена и вместо нее будет создана новая база.
Реализация системы CMS
Теперь, когда база данных создана, можно приступать к построению основной
части системы.
Интерфейсная часть
Начнем мы с рассмотрения сценария index. php, код которого показан в листинге
28.2. Этот сценарий выводит первую страницу, которая будет отображаться посети-
телям сайта. На первой странице должны отображаться заголовки последних статей
из каждой страницы.
Листинг 28.2. index.php — этот сценарий отображает наиболее новые
заголовки статей из каждой страницы
<?php
include_once('db_fns.php');
include_once('header.php');
$handle = db_connect();
$pages_sql = 'select * from pages order by code';
$pages_result = $handle->query($pages_sql);
echo '<table border="0” width="400”>';
while ($pages = $pages_result->fetch_assoc())
{
$story_sql = "select * from stories
where page = '{$pages['code']}'
and published is not null
order by published desc";
$story_result = $handle->query($story_sql);
if ($story_result->num_rows) {
$story = $story_result->fetch_assoc();
echo "<tr>
<td>
<h2>{$pages['description']}</h2>
<p>{$story['headline']}</p>
<p align='right' class='morelink'>
<a href='page.php?page={$pages['code']}' >
Читать дальше {$pages['code']} ...
</a>
</p>
</td>
<td width='100'>“ ;
if ($story['picture'])
{
echo '<img src="resize_image.php?image=';
echo urlencode($story['picture']);
echo '&max_width=80&max_height=60" />';
}
Глава 28. Разработка системы управления содержимым
627
echo '</td></tr>';
}
}
echo '</table>';
include_once('footer.php');
В сценарии index.php, как и во всех общедоступных сценариях, выполняется
включение файла header. php в начале и файла footer. php в конце. В результате лю-
бой вывод, генерируемый сценарием, отображается в ячейке, предназначенной для
вывода основного содержимого страницы.
Самая главная работа выполняется посредством двух запросов к базе данных.
Первый из них:
select * from pages order by code
извлекает список страниц, содержащихся в базе данных. Затем в теле цикла выполня-
ется второй запрос:
select * from stories
where page = '{$pages['code']}'
and published is not null
order by published desc
который ищет статьи на данной странице в обратном порядке даты их публика-
ции. Поскольку строка запроса заключена в двойные кавычки, конструкция
{$pages [' code' ]} заменяется элементами массива $pages.
На рис. 28.2 можно видеть вывод сценария index. php для введенных в базу тесто-
вых данных.
Рис. 28.2. Сценарий index.php выводит заго-
ловки из каждой страницы сайта
628
Часть V. Реальные проекты на РНР и MySQL
Рядом с каждым заголовком генерируется ссылка приблизительно такого вида:
<р align='right1 class='morelink'>
<a href='page.php?page=news'>
Читать дальше новости ...
</a>
</p>
Ссылка генерируется внутри рассмотренного выше цикла, который обеспечивает
вывод значения строки запроса раде и имени страницы рядом с соответствующим
заголовком. В результате щелчка на ссылке выводится страница, генерируемая сце-
нарием page. php. Она содержит полный список статей для определенной страницы.
Код сценария page. php можно найти в листинге 28.3.
Листинг 28.3. page .php — этот сценарий отображает на странице все опубликованные статьи
<?php
if (!isset($_REQUEST['page'])&&!isset($_REQUEST['story']))
{
header('Location: index.php');
exit;
)
$page = $_REQUEST['page'];
$story = intval($_REQUEST['story']);
include_once('db_fns.php');
include_once('header.php');
$handle = db_connect();
if(Sstory)
{
Squery = "select * from stories
where id = 'Sstory' and
published is not null";
}
else
{
Squery = "select * from stories
where page = 'Spage' and
published is not null
order by published desc";
)
Sresult = $handle->query(Squery);
while (Sstory = $result->fetch_assoc())
{
I/ заголовок
echo "<h2>{Sstory['headline']}</h2>";
I: изображение
if ($story['picture'])
{
echo '<div style="float:right; margin:Opx Opx 6px 6px;">';
echo '<img src="resize_image.php?image=';
echo urlencode(Sstory[picture]);
echo '&max_width=200&max_height=120” align = right/x/div>';
}
Глава 28. Разработка системы управления содержимым
629
11 строка подписи
$w = get_writer_record($story['writer’]);
echo ’<br /><p class="byline">';
echo $w[full_name].', ' ;
echo date('M d, H:i', $story['modified']);
echo '</p>';
// основной текст
echo $story['story_text'];
}
include_once('footer.php');
Обратите внимание, что сценарий page.php требует либо значения переменной
раде, либо значения переменной story. Когда сценарий page.php вызывается на-
прямую без строки запроса, первый условный оператор:
if (!isset($_REQUEST['page'])&&!isset($_REQUEST['story']))
{
header('Location: index.php');
exit;
1
отправляет посетителя обратно на страницу заголовков, чтобы отсутствие значения
раде не приводило к ошибке.
Первый запрос используется, если эта страница вызывается для отображения
одиночной статьи:
select * from stories
where id = 'Sstory' and
published is not null
Второй запрос служит для извлечения всех статей на указанной странице. С по-
мощью цикла загруженные изображения и тексты статей выводятся на экран; в конце
добавляется информация о том, кто подготовил статью, и дата ее последней модифи-
кации.
select * from stories
where page = '$page’ and
published is not null
order by published desc
На рис. 28.3 показана страница page. php в действии. На ней выводятся все эле-
менты страницы новостей, которые служат тестовыми данными для нашего прило-
жения.
Манипуляции изображениями
Возможно, авторы будут дополнять свои статьи самостоятельно полученными фо-
тографиями. Мы хотим достигнуть единообразия оформления, но что произойдет,
когда какой-то автор загрузит крупное изображение высокого качества, а другой ав-
тор — небольшую картинку с качеством миниатюры?
Основываясь на предположении, что изображения, в основном, будут фотогра-
фиями, можно ограничиться лишь форматом JPEG, а для манипулирования графикой
630
Часть V. Реальные проекты на РНР и MySQL
использовать соответствующие PHP-функции. Подробное обсуждение этой темы бы-
ло представлено в главе 21.
/ <Ученые Новости < юень Бьк 1|Х> • Mozffla {ftuiM И>1 :1
файл Ом**» инст^иш - "
N Д ’ X» ' <JL £ |jg'
вмямкв
^Сетевые Новости Очень Быстро
мужчины родился сын
Billings, Dec 12,02:16
| Сегодня в госпитале города Кони-Айленд у vr?2K«A
[мужчины родился сын Ребенок весом более
[грех килограмм чувствует себя отлично
Родители пребывают в диком восторге, поскольку это их
[первый сын, и радостно заявляют, что через несколько лету
>них будет очень большая семья
Зтец ребенка Тад. 34 лет. воспользовался новейшим методом
вынашивания плода, при котором эмбрион на ранней стадии
:воего развития пересаживается в организм отца Он уверен,
гго этот метод уменьшит множество рисков, связанных с
вождением
НЩртожар!
j ГОТОВО
PS-RS
Рис. 28.3. Сценарий page.php выводит все опубли-
кованные статьи на странице новостей
Мы написали простой сценарий resize_image.php, который на лету изменяет
размер изображения, в результате чего оно может выводиться с использованием
HTML-дескриптора <img>. Код этого сценария показан в листинге 28.4. Функция из-
менения размеров изображения на лету может оказаться неприемлемой для чрезмер-
но загруженных сайтов, поскольку она требует достаточно много вычислительной
работы. Использование упомянутой функции в нашем приложении позволит добить-
ся гибкости при отображении одного и того же изображения с разными размерами
на разных страницах — мелкие размеры на странице заголовков и крупные размеры
на странице соответствующей статьи. Кроме того, если в будущем потребуется изме-
нить компоновку сайта, этот сценарий также принесет немалую пользу.
Листинг 28.4. resize image. php — этот сценарий изменяет размеры JPEG-изображения на лету
<?php
$image = $-REQUEST[’image1];
$max_width = $-REQUEST[1max_width1];
$max_height = $-REQUEST['max_height'];
if (!$max_width)
$max_width = 80;
if (!$max_height)
$max_height = 60;
Ssize = GetlmageSize($image);
Глава 28. Разработка системы управления содержимым
631
Swidth = $size[O];
Sheight = $size[l];
$x_ratio = $max_width / Swidth;
$y_ratio = $max_height / Sheight;
if ( ($width <= $max_width) && (Sheight <= $max_height) ) {
$tn_width = Swidth;
$tn_height = Sheight;
}
else if (($x_ratio * Sheight) < $max_height) [
$tn_height = ceil($x_ratio * Sheight);
$tn_width = $max_width;
1
else {
$tn_width = ceil($y_ratio * Swidth);
$tn_height = $max_height;
)
$src = ImageCreateFromJpeg($image);
$dst = ImageCreate($tn_width,$tn_height);
ImageCopyResized($dst, $src, 0, 0, 0, 0,
$tn_width,$tn_height,Swidth,Sheight);
header('Content-type: image/jpeg');
ImageJpeg(Sdst, null, -1);
ImageDestroy($src) ;
ImageDestroy(Sdst);
Этот сценарий принимает три параметра: имя файла изображения, максимальную
ширину и максимальную высоту в точках. Не стоит полагать, что если указан макси-
мальный размер 200x200, то изображение будет масштабировано в соответствии с
этими значениями. Наоборот, масштаб изображения будет пропорционально умень-
шен таким образом, чтобы заданные максимальные размеры не превышались. На-
пример, изображение размером 400x300 будет уменьшено до размера 200x150. В ре-
зультате сохраняются пропорции изображения.
Динамическое (то бишь, на лету) изменение размеров изображения на сервере
гораздо предпочтительнее, нежели простое задание атрибутов height и width в деск-
рипторе <img>. Размер большого изображения с высоким разрешением может соста-
вить несколько Мбайт. Если же картинку уменьшить до приемлемых размеров, ее
размер может оказаться менее 100 Кбайт. Следовательно, нет надобности загружать
на сервер файл большого размера, а затем предлагать браузеру изменить размеры
изображения, однако на сайтах с большой нагрузкой имеет смысл кэшировать изме-
няемые изображения.
Функции манипуляции изображениями подробно рассматривались в главе 21.
Здесь для масштабирования' изображений на лету используется функция Image-
CopyResized().
Ключевой аспект операции изменения размеров состоит в вычислении новых па-
раметров ширины и высоты. При этом определяется соотношение между реальными
и максимальными размерами. Параметры $max_width и $max_height можно переда-
632
Часть V. Реальные проекты на РНР и MySQL
вать в одной строке запроса, в противном случае будут задействованы стандартные
значения, определенные в верхней части листинга.
$x_ratio = $max_width / $width;
$y_ratio = $max_height I Sheight;
Если изображение уже меньше заданных максимальных размеров, его ширина и
высота остаются неизменными. В противном случае будет использован коэффициент
по X или по Y для одинакового масштабирования обоих размеров, чтобы изображе-
ние уменьшенного размера не оказалось растянутым либо, наоборот, сплющенным:
if ( ($width <= $max_width) && (Sheight <= $max_height) ) {
$tn_width = $width;
$tn_height = Sheight;
}
else if (($x_ratio * Sheight) < $max_height) {
$tn_height = ceil($x_ratio * Sheight);
$tn_width = $max_width;
)
else {
$tn_width = ceil($y_ratio * Swidth);
$tn_height = $max_height;
)
После вычисления требуемых размеров изображение приводится к ним и отобра-
жается в браузере. Рассматриваемый сценарий вызывается внутри дескриптора <img>
на странице и в результате передает свой вывод непосредственно в браузер с исполь-
зованием соответствующего НТТР-заголовка.
Потенциальная польза данного подхода состоит в том, что изображения не долж-
ны храниться внутри дерева Web-документов. В результате существенно увеличивает-
ся защищенность сайта, и необходимо иметь только один сценарий для записи изо-
бражений в заданный каталог, который также должен находиться вне дерева Web-
документов.
Прикладная часть
Теперь давайте рассмотрим метод добавления статей в систему. Меню админист-
ратора отсылает авторов на сценарий writer.php. После аутентификации автора
этот сценарий выводит список написанных им статей. Он отображает дату публика-
ции для актуальных статей и предлагает опции добавления новой статьи, редактиро-
вания или удаления существующей, а также возможность установки ключевых слов
поиска. Пример можно видеть на рис. 28.4.
Эти экраны не форматируются внутри файлов заголовка и нижнего колонтитула,
хотя возможно и такое. Поскольку данные сценарии используются только авторами и
редактором, в нашем примере реализован лишь минимально необходимый объем
форматирования, который позволяет создать работоспособную систему. Код сцена-
рия writer. php показан в листинге 28.5.
Глава 28. Разработка системы управления содержимым
633
Рис. 28.4. Сценарий writer.php выводит страницу
управления статьями для авторов
Листинг 28.5. writer .php — этот сценарий реализует интерфейс, позволяющий
авторам управлять своими статьями
<?php
// writer.php реализует интерфейс, позволяющий авторам управлять
// своими статьями
include_once('include_fns.php');
if (Icheck_auth_user())
(
login_form();
}
else
(
Shandie = db_connect();
$writer = get_writer_record($_SESSION['auth_user1 ]);
echo '<p> Добро пожаловать, '.$writer('full_name'];
echo ' (<a href="logout.рЬр">Выйти</а>) (<a href="index.php”>MeHio</a>)
(<a href=". ./">Общедоступньгй сайт</а>) </p>';
echo '<p>';
Squery = 'select * from stories where writer = \''.
$_SESSION['auth_user'].1\' order by created desc';
Sresult = $handle->query(Squery);
echo 'Ваши статьи:
echo $result->num_rows;
echo ' (<a href="story.php">flo6aBHTb новую</а>)';
echo '</pxbr /><br />';
634
Часть V. Реальные проекты не РНР и MySQL
if ($result->num_rows)
{
echo 1<table>1;
echo '<tr><th>3aronoBOK</thxth>CTpaHMua</th>';
echo 1 <th>Co3BaHa</thxth>M3MeHeHa</thx/tr>' ;
while ($stories = $result->fetch_assoc())
{
echo 1<trxtd>';
echo $stories['headline1];
echo '</tdxtd>‘;
echo Sstories['page'];
echo '</tdxtd>';
echo date('M d, H:i', $stories['created']);
echo ' < / tdx td>' ;
echo datel'M d, H:i', $stories['modified']);
echo '</tdxtd>';
if ($stories['published'])
{
echo '[Опубликована '.dateCMd, H:i', $stories['published']).']1;
}
else
{
echo '[<a href="story,php?story='.$stories['id']
.'">редактировать</а>1 ';
echo '[<a href="delete_story.php?story='
.$stories['id'].'">удалить</а>] ';
}
echo '[<a href="keywords.php?story='.$stories['id']
. ' "жлючевые слова</а>] ' ;
echo '</tdx/tr>';
)
echo '</table>';
На первом этапе проверяется, успешно ли прошла аутентификация пользователя.
Если это не так, отображается только форма входной регистрации.
После входа автора в систему переменной сеанса auth_user присваивается значе-
ние. Используемая здесь аутентификация не особенно надежна. В реальной ситуации
потребуется обеспечить, чтобы аутентификация пользователей выполнялась надле-
жащим образом. Подробнее о том, как это сделать, говорится в главе 16.
Информация, введенная в форме входной регистрации, передается в сценарий
login.php, который сравнивает имя пользователя и пароль с соответствующими зна-
чениями базы данных. В случае успешного входа пользователь перемещается на стра-
ницу, на которой он пребывал ранее, с помощью значения HTTP_REFERER. Это означа-
ет, что сценарий входа в систему может вызываться из любой страницы приложения.
Затем автору выдается приветствие с указанием его имени и предоставляется воз-
можность выхода из системы. Эта ссылка всегда отображается в верхней части стра-
ницы writer, php, что позволяет легко выйти из системы в любой момент.
Глава 28. Разработка системы управления содержимым
635
$writer = get_writer_record($_SESSION['auth_user']);
echo '<p> Добро пожаловать, '.Swriter['full_name'];
echo ' (<a href="logout.php">BbifiTM</a>)
(<a href=''index.php">MeHK></a>)
(<a href="../">Обшедоступный сайт</а>) </p>';
Функция get_writer_record(), определенная в библиотеке db_fns.php, возвра-
щает массив из всех полей таблицы автора на основе переданного имени пользовате-
ля. Сценарий logout. php просто сбрасывает значение переменной auth_user.
Следующий SQL-запрос в writer.php выбирает все статьи автора, начиная с до-
бавленных за последнее время:
$query = 'select * from stories where writer = \''.
$-SESSION['auth_user'].'\' order by created desc';
Для каждой записи, связанной со статьей, хранятся метки времени добавления,
модификации и публикации статьи. Когда добавляется новая статья, меткам создания
и модификации присваивается текущее системное время. Любые последующие изме-
нения статьи будут приводить лишь к обновлению метки модификации. Метка вре-
мени публикации не устанавливается до тех пор, пока редактор в действительности
не опубликует статью, сделав ее частью сайта.
Вся эта информация выводится в окне статей, сначала с помощью кода:
echo date('M d, H:i', Sstories['created']);
затем:
echo date('M d, H:i', Sstories['modified']);
и, наконец:
if (Sstories['published'])
{
echo '[Опубликована '.date('Md, H:i', Sstories['published';
}
else
{
echo '[<a href="story,php?story='.Sstories['id'].'”>редактировать</а>] ';
echo '[<a href="delete_story.php?story='.Sstories['id'].'”>удалить</а>] ’;
}
echo ’ [<a href =" keywords. php?story=' .Sstories! 'id' ].' "жлючевые слова</а>] ' ;
Этот фрагмент кода выводит только дату публикации, если таковая существует.
В противном случае выводятся ссылки для редактирования или удаления статьи. Клю-
чевые слова можно добавлять как к опубликованным, так и к неопубликованным
статьям.
Сценарий ввода новой статьи или редактирования существующей содержится в
файле story.php. На рис. 28.5 показан вывод этого сценария во время редактирова-
ния статей тестовой базы данных.
636
Часть V. Реальные проекты на РНР и MySQL
Рис. 28.5. Сценарий story.php позволяет редакти-
ровать статью
Полный исходный код сценария story.php представлен в листинге 28.6.
Листинг 28.6. story.php — этот сценарий реализует создание и редактирование статьи
«?php
include ('include_fns.php');
if (isset($_REQUEST!'story']))
{
$story = get_story_record($_REQUEST['story']);
}
«form action="story_submit.php" method=”post“ enctype="multipart/form-data">
«input type="hidden" name="story" value="«?php echo $_REQUEST['story’];?>">
«input type="hidden” name="destination"
value="«?php echo $_SERVER['HTTP_REFERER'];?>“>
«table>
<tr>
<td>3aronoBOK«td>
</tr>
<tr>
«tdxinput size="80" name=“headline"
value="«?php echo $story['headline'];?>"></td>
</tr>
Глава 28. Разработка системы управления содержимым
637
<tr>
<td>CTpaHwua</td>
</tr>
<tr>
<td>
<?php
if(isset($_REQUEST['story']))
{
$query = "select p.code, p.description
from pages p, writer_permissions wp, stories s
where p.code = wp.page
and wp.writer = s.writer
and s.id =".$_REQUEST['story'];
}
else
{
$query = "select p.code, p.description
from pages p, writer_permissions wp
where p.code = wp.page
and wp.writer = '{$_SESSION['auth_user']}'";
}
echo query_select('page', $query, $story['page']);
</td>
</tr>
<tr>
<td>TeKCT статьи (может содержать HTML-дескрипторы)</td>
</tr>
<tr>
etdxtextarea cols="80" rows="7" name="story_text"
wrap="virtual"><?php echo $story [' story_text' ] ; ?x/textarea>
</td>
</tr>
<tr>
<td>JlM6o загрузите HTML-файле/td>
</tr>
<tr>
etdxinput type="file" name="html" size="40"x/td>
</tr>
<tr>
<td>PncyHOK<Itd>
e/tr>
<tr>
etdxinput type="file" name="picture" size="40"x/td>
</tr>
<?php
if ($story[picture])
{
$size = getlmageSize('../'.$story['picture' ] ) ;
$width = $size[0];
$height = $size[l];
638
Часть V. Реальные проекты на РНР и MySQL
<tr>
<td>
cimg src="<?php echo Sstory['picture'];?>”
width=”c?php echo $width;?>" height=”<?php echo Sheight;?>”>
< / td>
< tr>
<?php
<tr>
etc align="center"xinput type="submit” value=”Отправить"></td>
</table>
</ form>
Для добавления статей и их редактирования используется один и тот же сцена-
рий. Выполняемое действие зависит от того, установлено ли значение переменной
story при вызове сценария.
if isset($_REQUEST['story']))
Sstory = get_story_record($_REQUEST['story']);
Функция get_story_record() определена в библиотеке db_fns.php. Она возвра-
щает массив всех полей таблицы статей для заданного идентификатора статьи. Если
идентификатор не передается, переменная Sstory получает значение NULL и $story
не будет содержать элементов массива.
ctdxinput size=”80” name="headline”
value="<?php echo Sstory['headline'];?>"></td>
Если значение переменной Sstory не установлено, предыдущий фрагмент кода не
выдаст какого-либо значения из PHP-оператора, поэтому поле ввода заголовка будет
пустым. Когда значение переменной Sstory установлено, она содержит текст заго-
ловка для редактируемой статьи.
Функция query_select() определена в библиотеке db_fns.php. Она возвращает
HTML-код списка <select> на основе заданного SQL-запроса. Первый параметр
представляет собой атрибут name для конструкции <select>. SQL-запрос из второго
параметра выбирает два столбца, причем первый из них является атрибутом value
каждой опции, а второй следует после дескриптора <option> и представляет собой
текст, отображаемый в списке. Третий параметр необязателен. Он добавляет атрибут
selected к опции, значение которой совпадает с указанным.
Функция query_select () используется для генерации HTML-дескриптора
<select>, содержащего страницы, на которые пользователь имеет право помещать
свои статьи.
Для сохранения редактируемой статьи под тем же идентификатором, который
она имела ранее, необходимо записать этот идентификатор. Для этого используется
скрытая переменная:
cinput type=”hidden" name="story" value="<?php echo $_REQUEST['story’];?>’>
Глава 28. Разработка системы управления содержимым
639
Приведенная строка кода создает переменную-заполнитель за счет установки но-
вого значения для статьи на основе значения, переданного в переменной $ story. По-
сле отправки формы сценарий story_submit.php проверяет, имеет ли переменная
$ story значение, и генерирует в соответствии с этим SQL-оператор UPDATE либо
INSERT.
Код сценария s tory_submi t. php можно найти в листинге 28.7.
Листинг '28.7. story submit .php — этот сценарий реализует вставку и обновление
статьи в разе данных
<?php •
// stctty_submit.php
// Добавляет/изменяет запись для статьи
includte_once('include_fns.php');
Shandite = db_connect();
Sheadline = $_REQUEST['headline'];
Spage = $_REQUEST['page'];
Stime ;= timed ;
if ( (isset($_FILES['html']['name']) &&
(dirname($_FILES['html']['type']) == 'text') &&
is_uploaded_file($_FILES['html']['tmp_name'])))
{
$story_text = file_get_contents($_FILES['html'1('tmp_name' ]) ;
}
else
{
$story_text = $_REQUEST['story_text1];
}
$story_text = addslashes($story_text);
if (isset($_REQUEST['story']) && $_REQUEST['story']!='')
{ // Выполняется обновление существующей статьи
$Story = $_REQUEST['story'];
Squery = "update stories
set headline = 'Sheadline',
story_text = '$story_text',
page = 'Spage',
modified = Stime
where id = $story“;
}
else
{ // Выполняется добавление новой статьи
Squery = "insert into stories
(headline, story_text, page, writer, created, modified)
values
('Sheadline', '$story_text', 'Spage', '".
$_SESSION['auth_user']."', Stime, Stime)";
}
Sresult = $handle->query(Squery);
640
Часть V. Реальные проекты на РНР и MySQL
if (!$result)
{
echo "Ошибка базы данных во время выполнения запроса <pre>$query</pre>";
echo mysqli_error();
exit ;
}
if ( (isset($_FILES['picture']['name']) &&
is_uploaded_file($_FILES['picture']['tmp_name'])))
{
if (!isset($_REQUEST['story']) || $_REQUEST['Story']=='')
{
Sstory = mysqli_insert_id(Shandie);
}
Stype = basename($_FILES['picture']['type']);
switch ($type) {
case 'jpeg':
case 'pjpeg': Sfilename = "images/Sstory.jpg";
move_uploaded_file($_FILES['picture']['tmp_name'],
'../'.$filename);
Squery = "update stories
set picture = 'Sfilename'
where id = Sstory";
Sresult = $handle->query(Squery);
break;
default: echo 'Недопустимый формат файла рисунка: '.
$_FILES['picture']['type'];
}
}
header('Location: '.$_REQUEST['destination']);
Ссылка, обеспечивающая удаление статьи, связана с вызовом сценария
delete_story.php, который выполняет простой SQL-оператор DELETE и возвра-
щает автора на страницу, откуда ссылка была активизирована. Код сценария
delete_story. php показан в листинге 28.8.
Листинг 28.8. delete story. php — этот сценарий реализует удаление статьи из базы данных
<?php
' delete_story.php
include_once('include_fns.php');
Shandie = db_connect();
Sstory - $_REQUEST['story'];
if(check_permission($_SESSION['auth_user'], Sstory))
{
Squery = "delete from stories where id = Sstory";
Sresult = $handle->query(Squery);
}
header('Location: '.$_SERVER['HTTP_REFERER' ] ) ;
Глава 28. Разработка системы управления содержимым
641
Обратите внимание, что функция check_permission () обращается к базе данных
для выяснения, имеет ли пользователь, вошедший в систему, права удалять статьи с
данной страницы. В разделе, относящемся к администрированию, эта функция ис-
пользуется на многих страницах. Даже если неавторизованные пользователи не будут
видеть ссылки на удаление статей, не очень хорошо, когда пользователи будут иметь
шанс скомпрометировать систему безопасности, просто угадав URL-адрес.
Поиск статей
В результате щелчка на ссылке ключевые слова в списке статей вызывается новая
форма для ввода ключевых слов, связанных со статьей. Количество ключевых слов не
ограничено. Каждому из них присваивается весовой коэффициент. Более высокие
значения весового коэффициента соответствуют более важным ключевым словам.
На рис. 28.6 показано окно, используемое для назначения ключевых слов поиска
для определенной статьи.
Рис. 28.6. Этот экран дает возможность устанавли-
вать ключевые слова для статьи
Сценарий keywords. php достаточно прост, поэтому подробно рассматривать мы
его не будем. Его можно найти на прилагаемом к книге компакт-диске. Этот сценарий
запускает сценарии keyword_add.php и keyword_delete.php, которые также весьма
просты и здесь не рассматриваются.
Для ввода новых ключевых слов в базу данных сценарий keyword_add.php исполь-
зует следующий запрос:
insert into keywords (story, keyword, weight)
values ($story, '$keyword', $weight)
642
Часть V. Реальные проекты на РНР и MySQL
Совершенно аналогично, сценарий keyword_delete.php для удаления ключевого
слова использует такой запрос:
delete from keywords where story = $story and keyword = '$keyword'
Интерес представляет способ использования весовых коэффициентов для вычис-
ления процентного отношения значимости статьи в процессе поиска.
Поисковая форма, генерируемая сценарием search_form.php, содержит единст-
венное поле для ключевых слов и передается в сценарий search.php, который за-
прашивает базу данных статей с целью поиска соответствующего содержимого. Код
сценария search. php показан в листинге 28.9.
Листинг 28.9. search.php — этот сценарий выполняет поиск статей по ключевым словам
и вычисляет процентное отношение значимости д ля совпадающих с критериями статей
<?php
include_once('db_fns.php');
include_once('header.php');
$handle = db_connect();
i f ($_REQUEST['keyword'])
{
$keywords = split(' $_REQUEST['keyword']);
$num_keywords = count($keywords);
for ($i=0; $i<$num_keywords; $i++)
{
if ($i)
{
Skeywords_string .= "or k.keyword = '”.$keywords[$i]."'
}
else
{
$keywords_string .= "k.keyword = '".$keywords[$i]........;
}
}
Squery = "select s.id,
s.headline,
10 * sum(k.weight) / $num_keywords as score
from stories s, keywords k
where s.id = k.story
and ($keywords_string)
and published is not null
group by s.id, s.headline
order by score desc, s.id desc";
$result = $handle->query(Squery);
}
echo ' <Ь2>Результаты псиска</112> ' ;
if (Sresult && $result->num_rows)
{
echo '<table>';
while ($matches = $result->fetch_assoc())
{
Глава 28. Разработка системы управления содержимым
643
echo "ctrxtdxa href='page?story={$matches['id']}'>
{Smatches[1 headline']}
</tdxtd>" ;
echo floor($matches['score']).'%';
echo '</td></tr>';
}
echo '</table>';
)
else
{
echo 'He найдено ни одной статьи, соответствующей критериям поиска';
}
include_once('footer.php'); * i * * * * * * В * 10
Сначала строка с ключевыми словами, передаваемая в сценарий search.php, раз-
бивается на отдельные искомые слова. В этом примере с целью упрощения мы реши-
ли не разрабатывать какие-то расширенные методы поиска, скажем, с возможностью
применения операций AND и OR либо с возможностью объединения слов в фразу: в
конечном счете все слова в строке являются ключевыми.
i f ($_REQUEST['keyword'])
{
$keywords = split(' ', $_REQUEST['keyword']);
$num_keywords = count($keywords);
for ($i=0; $i<$num_keywords; $i++)
{
if ($i)
{
$keywords_string .= "or k.keyword = '".$keywords[$i]."' ";
)
else
{
$keywords_string .= "k.keyword = '".Skeywords[SiJ.........;
}
}
В приведенном коде с помощью PHP-функции split!) создается массив, содер-
жащий слова из строки ключевых слов, разделенные пробелами. Если задано только
одно слово, возвращается массив с единственным элементом, и последующий цикл
выполняется один раз.
В результате условие, хранимое в переменной $keywords_string, будет иметь при-
близительно такой вид:
k.keyword = 'keywordl' or к.keyword = 'keyword2' or к.keyword = keyword!'
Ниже показан поисковый запрос, который основан на рассмотренном выше коде:
select s.id,
s.headline,
10 * sum(k.weight) I $num_keywords as score
from stories s, keywords k
where s.id = k.story
and (k.keyword = 'keywordl'
or k.keyword = 'keyword!'
644
Часть V. Реальные проекты на РНР и MySQL
or к.keyword = 'keywords')
group by s.id, s.headline
order by score desc, s.id desc
Общий показатель значимости определяется как сумма весовых коэффициентов
всех совпадающих ключевых слов, деленная на количество искомых ключевых слов и
умноженная на 10. Эта формула пригодна для поиска, когда все введенные ключевые
слова соответствуют ключевым словам из базы данных.
Поскольку значения весовых коэффициентов лежат в диапазоне от 1 до 10, мак-
симальное значение общего показателя значимости равно 100. Подобная величина
для поиска по трем ключевым словам достигается, если все три слова найдены, и ка-
ждое из них имеет весовой коэффициент 10.
Окно редактора
На текущий момент нерассмотренной осталась одна составляющая проекта, кото-
рая отвечает за публикацию статьи после ее написания. Это осуществляет сценарий
publish .php, код которого представлен в листинге 28.10.
Листинг 28.10. publish.php — этот сценарий выводит список всех документов, в рамках
которого редактор может выбирать документы, предназначенные д ля отображения на сайте
<?php
include_once('include_fns.php');
if (!check_auth_user())
{
login_form();
}
else
{
$handle = db_connect();
$writer = get_writer_record($_SESSION['auth_user']);
echo '<р>Добро пожаловать, '.$writer['full_name'];
echo ' (<a href=”logout,рЬр">Выход</а>) (<a href="index.php">MeHK></a>)
(<a href="../">Общедоступный сайт</а>) </p>';
$query = "select * from stories s, writer_permissions wp
where wp.writer = '{$_SESSION[1auth_user']}' and
s.page = wp.page
order by modified desc";
$result = $handle->query($query);
echo '<Ь1>Редактор-администратор</Ь1>';
echo '<table>';
echo '<tr><th>3aronOBOK</th><th>MoflM$mjnpoBaHa</thx/tr>';
while ($story = $result->fetch_assoc())
{
echo ' <trxtd> ' ;
echo $story['headline'];
echo '</tdxtd>';
echo datef'M d, H:i', $story['modified']);
echo '</td><td>';
if ($story[published])
Глава 28. Разработка системы управления содержимым
645
echo '[<a href="unpublish_story.php?story='.$story['id']
.'">не публиковать</a>] ';
}
else
{
echo '[<a href="publish_story.php?story=1.$story['id' ]
.'">публиковать</а>] ';
echo '[<a href="delete_story.php?story='.$story['id']
.'">удалить</а>]
}
echo '[<a href="story.php?story='.$story['id']
.''>редактировать</а>]
echo ' </tdx/tr> ' ;
}
echo '</table>';
}
?> * i * * * * * * В
Этот сценарий должен быть доступен только для лиц, уполномоченных публико-
вать статьи на сайте. В контексте нашего приложения таковым является любой, кому
разрешено отправлять статьи на какую-нибудь страницу. В реальной системе, конеч-
но же, полномочия должны разграничиваться, поскольку тот, кто публикует статью
на сайте, должен ее предварительно вычитать и утвердить материал.
Сценарий publish.php очень похож на stories .php, с той лишь разницей, что для
редактора отображаются статьи всех авторов, а не только его собственные. Оператор
i f обеспечивает предоставление для каждой статьи необходимых опций. Публикацию
статей можно отменять, а неопубликованные статьи можно публиковать либо удалять.
Связанные с этим оператором три ссылки вызывают, соответственно, сценарии
unpublish_story.php, publish_story.php и delete_story.php.
В сценарии publish_story .php используется следующий SQL-запрос:
update stories set published = $now
where id = $story
Этот запрос помечает статью как опубликованную и разрешает ее общедоступный
просмотр.
Аналогично, сценарий unpublish_story.php использует следующий запрос, что-
бы пометить статью как неопубликованную и запретить ее всеобщий просмотр:
update stories set published = null
where id = $story
Ссылка на редактирование статьи отображается независимо от факта публикации
статьи, поэтому редактор, при необходимости, всегда может внести в нее изменения.
В этом-то и заключается отличие от уровня доступа для автора, который может изме-
нять статью лишь до ее публикации.
646
Часть V. Реальные проекты на РНР и MySQL
Расширение проекта
Как и в подавляющем большинстве учебных проектов, существует множество пу-
тей расширения, которые позволят сделать систему управления содержимым более
совершенной:
Можно разрешить группам пользователей совместно работать над коллектив-
ными статьями.
Можно реализовать более гибкую компоновку страницы, чтобы редакторы
могли позиционировать текст и изображения в рамках страницы.
Можно организовать библиотеку изображений, чтобы часто используемые ри-
сунки не дублировались. Кроме того, с изображениями, как и текстом, можно
было бы связывать ключевые слова для целей поиска.
Можно добавить функции проверки орфографии во время редактировании
текста статьи. Для реализации такого рода проверки можно воспользоваться,
например, библиотекой Ispell или Aspell.
Что дальше
В следующей главе мы рассмотрим проект, связанный с построением Web-
интерфейса, который позволил бы получать и отправлять сообщения электронной
почты в среде Web с использованием протокола IMAP.
Глава 28. Разработка системы управления содержимым
647
29
Разработка почтовой
Web-службы
В настоящее время сайты все чаще и чаще начинают предоставлять пользовате-
лям услуги электронной почты, основанной на Web. В этой главе мы рассмот-
рим, как реализовать Web-интерфейс с существующим почтовым сервером с исполь-
зованием PHP-библиотеки IMAP. С помощью созданного интерфейса можно будет
просматривать содержимое собственного почтового ящика на Web-странице. Кроме
того, проект можно расширить и получить, в конечном счете, многопользователь-
скую почтовую Web-систему наподобие Hotmail.
В рамках этого проекта мы планируем разработать почтовый клиент “Свежая поч-
та” (“Warm Mail”), который предложит пользователям следующие функциональные
возможности:
Подключение к своим учетным записям на почтовых серверах POP3 или IMAP.
Чтение сообщений электронной почты.
Отправка сообщений электронной почты.
Отправка ответов на сообщения электронной почты.
Переадресация сообщений электронной почты.
Удаление сообщений из своего почтового ящика.
Задача
Для того чтобы обеспечить пользователю возможность чтения сообщений элек-
тронной почты, необходимо найти способ подключения к соответствующему почто-
вому серверу. Обычно почтовый сервер содержится на той же машине, что и Web-
сервер.
Мы должны наладить взаимодействие с почтовым ящиком пользователя, дабы
просматривать список принятых сообщений и индивидуально обрабатывать каждое
из них.
Для чтения сообщений из пользовательских почтовых ящиков почтовые серверы
поддерживают два основных протокола: POP3 и IMAP. По возможности, в проекте
потребуется реализовать поддержку обеих протоколов. Аббревиатура POP3 означает
Post Office Protocol (Почтовый протокол) версии 3, a IMAP— Internet Message Access Protocol
(Протокол доступа к сообщениям Internet).
Основное отличие между упомянутыми протоколами заключается в том, что POP3
предназначен и обычно применяется пользователями, которые подключаются к сети
на довольно короткое время с целью загрузки и удаления сообщений электронной
почты из сервера. Протокол IMAP ориентирован на применение в режиме постоян-
ного подключения к сети, и служит для постоянного взаимодействия с почтовой
службой, поддерживаемой на удаленном сервере. Протокол IMAP обладает рядом
усовершенствованных возможностей, которые в настоящем проекте не задействова-
ны ввиду его учебного характера.
Различия между протоколами исчерпывающе описаны в RFC-документах (RFC
1939 для версии POP3 и RFC 3501 для версии IMAP 4 revl). Кроме того, по приведен-
ному ниже адресу можно найти прекрасную статью со сравнительным анализом про-
токолов:
http://www.imap.org/papers/imap.vs.pop.brief.html
Ни один из упомянутых протоколов не предназначен для отправки почты — для
этого необходимо пользоваться протоколом SMTP (Simple Mail Transfer Protocol —
простой протокол передачи электронной почты), которым мы ранее пользовались в
рамках PHP-кода, когда вызывали функцию mail (). Этот протокол описан в докумен-
те RFC 821.
Компоненты решения
В РНР реализована обширная поддержка протоколов IMAP и POP3, однако она
предоставляется через библиотеку функций IMAP. Для того чтобы можно было эф-
фективно выполнять код, представленный в этой главе, потребуется установить биб-
лиотеку IMAP. Вы можете узнать, установлена ли эта библиотека в системе, внима-
тельно просмотрев информацию, которую выводит функция phpinfо ().
Если вы работаете в среде Linux или Unix и библиотека IMAP не установлена, по-
требуется загрузить необходимые библиотеки. Последняя версия доступна на FTP-
сайте по следующему адресу:
ftp://ftp.cac.washington.edu/imap/
В Unix-системе вы должны выгрузить исходный код и скомпилировать его для
своей операционной системы. Некоторые пользователи сообщали о сложностях с
компиляцией последних версий исходного кода IMAP вместе с РНР. Если вы сталки-
ваетесь с какими-то трудностями, имеет смысл взять старую, однако стабильно рабо-
тающую версию IMAP-2001.
Далее потребуется создать каталог для файлов IMAP внутри системного включае-
мого каталога с именем, скажем, imap. (Не копируйте файлы 1МАР непосредственно в
системный включаемый каталог, поскольку это может привести к конфликтам.)
Внутри созданного каталога создайте два подкаталога imap/lib/ и imap/include/.
Скопируйте все файлы *.h из места инсталляции в imap/include/. После ус-
пешной компиляции будет создан файл c-client.a. Переименуйте это файл на
libc-client .а и скопируйте его в imap/lib/.
После этого необходимо запустить PHP-сценарий конфигурирования, добавив
директиву --with-imap=dirname (где dirname — имя созданного вами ранее каталога)
к набору директив, используемых в вашей системе, и перекомпилировать РНР.
Глава 29. Разработка почтовой Web-службы
649
Для доступа к расширению IMAP под Windows потребуется открыть файл php. ini
и удалить символ комментария в строке:
extension=php_imap.dll
После этого следует перезапустить Web-сервер.
Проверить, установлено ли расширение IMAP, можно с помощью функции
phpinf о (). В выводе этой функции должен присутствовать раздел, касающийся IMAP.
Очень интересно отметить, что IMAP-функции, несмотря на свое название, оди-
наково хорошо взаимодействуют как с протоколом POP3, так и с протоколом NNTP
(Networks News Transfer Protocol — протокол передачи сетевых новостей). Мы будем
пользоваться IMAP-функциями для протоколов IMAP и POP3, тем не менее, прило-
жение “Свежая почта” можно очень просто расширить, превратив его в средство
чтения сетевых новостей по протоколу NNTP.
Эта библиотека содержит очень много функций, но для данного приложения по-
требуется обращаться лишь к нескольким из них. Функции будут рассматриваться в
процессе их использования. Если вы преследуете иные цели либо вам необходимо
реализовать в рамках приложения дополнительные функциональные возможности,
стоит обратиться к документации.
Можно построить довольно эффективное почтовое приложение, воспользовав-
шись лишь частью встроенных функций. Это означает, что достаточно проработать
лишь небольшую часть документации. Ниже перечислены IMAP-функции, которые
задействуются в данной главе:
imap_open()
imap_close()
imap_headers()
imap_header()
imap_fetchheader()
imap_body()
imap_delete()
imap-expunge()
Для того чтобы пользователь смог читать сообщения электронной почты, необ-
ходимо получить информацию о его почтовом сервере и учетной записи. Чтобы
пользователю не приходилось вводить эти сведения каждый раз, для их хранения мы
планируем создать базу данных имен пользователей и паролей.
Часто пользователи имеют более одной учетной записи электронной почты (на-
пример, одну для домашней переписки и одну' — для рабочей). Стало быть, потребует-
ся предоставить пользователям возможность подключения к любой своей учетной
записи. Таким образом, мы должны обеспечить хранение в базе данных нескольких
наборов сведений об учетных записях для каждого пользователя.
Пользователи должны иметь возможность читать сообщения, отвечать на них,
пересылать и удалять существующие сообщения, а также отправлять новые. Все
функции чтения можно возложить на протоколы IMAP и POP3, а операции отправ-
ки — на протокол SMTP, активизируемый функцией mail ().
Давайте рассмотрим, как объединить все составляющие проекта.
650
Часть V. Реальные проекты на РНР и MySQL
Обзор решения
Общая схема почтовой системы, основанной на Web, не особенно отличается от
других почтовых клиентов. Блок-схема системы и ее модулей показана на рис. 29.1.
Рис. 29.1. Интерфейс приложения “Свежая почта” предостав-
ляет пользователю функциональность уровня почтового ящика
и уровня сообщений
Как видно из диаграммы, сначала пользователь должен войти в систему, а затем
ему предоставляется список опций. Пользователь может' создать новую учетную за-
пись либо выбрать для использования одну из существующих. Кроме того, пользова-
тель может просматривать входящие сообщения, отвечать на них, переадресовывать,
удалять, а также отправлять новые сообщения.
Помимо этого пользователю доступна опция просмотра заголовков с дополнитель-
ной информацией, которые связаны с определенным сообщением. Просмотр всех за-
головков позволяет многое узнать о сообщении. Через заголовки можно определить, с
какого компьютера было отправлено сообщение электронной почты; понятно, что это
весьма удобное средство отслеживания нежелательных сообщений (спама). Опять-
таки, можно узнать, какие машины пересылали сообщение и время его поступления на
каждый хост — это удобно для предъявления претензий по поводу задержки сообще-
ний. Кроме того, если приложение добавляет в заголовки дополнительную информа-
цию, можно определить, каким почтовым клиентом пользовался отправитель.
В этом проекте мы разработали слегка отличающуюся архитектуру приложения.
Вместо набора сценариев, по одному для каждого модуля, используется сценарий
больших размеров, index.php, который функционирует подобно циклу обработки
событий программы с графическим интерфейсом пользователя. Каждое действие на
сайте, вызванное щелчком на кнопке, возвращает управление сценарию index.php,
но со своим параметром. В зависимости от параметра вызываются различные функ-
Глава 29. Разработка почтовой Web-службы
651
ции, которые обеспечивают вывод пользователю необходимого содержимого. Как
обычно, функции содержатся в библиотеках.
Такая архитектура пригодна для небольших приложений наподобие данного. Это
могут быть приложения, управляемые событиями, когда определенные функции за-
пускаются действиями пользователя. Организация единственного обработчика собы-
тий не очень удобна для крупных приложений либо проектов, разрабатываемых
группой программистов.
Перечень файлов проекта “Свежая почта” представлен в табл. 29.1.
Таблица 29.1. Файлы приложения “ Свежая почта”
Имя Tun Описание
index.php Приложение Главный сценарий, реализующий приложение в целом.
include_fns.php Функции Набор включаемых файлов приложения.
data_valid_fns.php Функции Набор функций проверки допустимости вводимых данных.
do_fns.php Функции Набор функций подключения к базе данных mail.
mail_fns.php Функции Набор связанных с электронной почтой функций для открытия почтовых ящиков, чтения сообщений и про- чих действий.
output_fns.php Функции Набор функций для вывода HTML-содержимого.
user_auth_fns.php Функции Набор функций аутентификации пользователей.
create_database.sql SQL SQL-код создания базы данных mail и регистрации пользователей.
А сейчас перейдем к обзору приложения.
Создание базы данных
База данных для приложения “Свежая почта” достаточно проста, поскольку' мы не
планируем хранить в ней сами сообщения электронной почты.
Все что необходимо хранить, так это сведения о пользователях системы. Для каж-
дого пользователя должны существовать следующие поля:
username — выбранное пользователем имя для приложения “Свежая почта”.
password — выбранный пользователем пароль.
address — указанный пользователем адрес электронной почты, который будет
отображаться в поле “From” (“От”) сообщений, отправляемых из системы.
displayname — “читабельное” имя, выбранное пользователем для отображения
в отправляемых сообщениях.
Кроме того, потребуется хранить следующие данные каждой учетной записи:
username — имя пользователя приложения “Свежая почта”, которому принад-
лежит учетная запись.
server — машина, на которой размещена учетная запись, например, localhost
или mail.tangledweb.com.au.
652
Часть V. Реальные проекты на РНР и MySQL
port — порт, к которому выполняется подключение с использованием данной
учетной записи. Обычно для РОРЗ-серверов применяется номер порта 110, а
для IMAP-серверов --143.
type — протокол, используемый для подключения к данному серверу — POP3
ИЛИ IMAP.
remoteuser — имя пользователя для подключения к почтовому серверу.
accountid — уникальный ключ для идентификации учетных записей.
Для создания базы данных служит SQL-запрос, показанный в листинге 29.1.
Листинг 29.1. create database.sql — SQL-запрос для создания почтовой базы данных mail
create database mail;
use mail;
create table users
(
username char(16) not null primary key,
password char(40) not null,
address char(100) not null,
displayname char(100) not null
) ;
create table accounts
(
username char(16) not null,
server char(100) not null,
port int not null,
type char(4) not null,
remoteuser char(50) not null,
remotepassword char(50) not null,
accountid int unsigned not null auto_increment primary key
) ;
grant select, insert, update, delete
on mail.*
to mail@localhost identified by 'password';
He забывайте, что этот SQL-запрос выполняется с помощью следующей команд-
ной строки:
mysql -u root -р < create_database.sql
Кроме того, потребуется ввести свой пароль привилегированного пользователя
(root). Перед выполнением запроса необходимо изменить пароль пользователя поч-
товой службы в файлах create_database. sql и db_f ns. php.
На прилагаемый к книге компакт-диск дополнительно помещен файл populate . sql
в каталог для данной главы. В этом приложении мы не собираемся реализовывать
процесс регистрации пользователей либо администрирования. В том случае, если
планируется крупномасштабное использование приложения, это можно сделать са-
мостоятельно. Для личного применения достаточно будет ввести в базу данных свои
данные. Сценарий populate.sql предоставляет для этого шаблон. Чтобы стать поль-
Глава 29. Разработка почтовой МвЬ-службы
653
зователем, можно ввести в соответствие с этим шаблоном свои данные и запустить
populate. sql на выполнение.
Архитектура сценария
Как уже упоминалось ранее, в этом приложении всем управляет один единствен-
ный сценарий — index.php. Его код приведен в листинге 29.2. Поскольку сценарий
довольно-таки длинный, мы последовательно рассмотрим все его разделы.
Листинг 29.2. index.php — основа системы “Свежая почта”
<?php
// Этот файл служит основой приложения "Свежая почта".
//Он функционирует, главным образом, как конечный автомат,
// и генерирует для пользователей вывод в зависимости от
// выполняемых ими действий.
^***********************************************+****************************
// Этап 1. Предварительная обработка
// Выполнение всех необходимых операций перед отправкой заголовка
// страницы и выбор информации, отображаемой в заголовках страницы
/y/****************W*************************************-********************-**
include (1include_fns.php');
session_start();
// Создать короткие имена переменных
$username = $_POST['username'];
$passwd = $_POST['passwd'];
$action = $_REQUEST['action'];
$account = $_REQUEST[1 account'];
$messageid = $_GET['messageid'];
$t0 = $_POST['to'];
$cc = $_POST['cc'];
$sdbject = $_POST[’subject1];
$message = $_POST['message'];
$buttons = arrayO;
// Добавлять к этой строке, если что-то выполняется перед выводом заголовка
$status = 1';
// Необходимо сначала обработать запросы на вход и выход из системы
if($username || $password)
{
if(login($username, $passwd))
{
$status .= '<р>Вы успешно вошли в систему. </pxbr /><Ьг /><Ьг /><Ьг />
<br /><Ьг />';
$_SESSION['auth_user'] = $username;
if(number_of_accounts($_SESSION['auth_user']) == 1)
{
$accounts = get_account_list($_SESSION['auth_user']);
$_SESSION[1selected_account'] = $accounts[0];
}
}
654
Часть V. Реальные проекты на РНР и MySQL
else
{
$status .= '<р>Извините, вход в систему с данным именем пользователя
и паролем невозможен. </pxbr /><Ьг /хЬг /><Ьг /><Ьг /><Ьг />';
}
}
if(Section == 'log-ouc')
{
session_destroy();
unset($action);
$_SESSION = array));
}
// Перед отображением заголовка необходимо обработать запросы
// на выбор, удаление и сохранение учетных записей
switch ( $action )
{
case 'delete-account' :
{
delete_account($_SESSION['auth_user'], $account) ;
break;
}
case 'store-settings' :
r
I
store_account_settings($_SESSION['auth_user'], $_POST);
break;
}
case 'select-account' :
{
// Если выбрана допустимая учетная запись,
// сохранить ее в переменной сеанса
if($account&&account_exists($_SESSION['auth_user'], $account))
(
$_SESSION['selected_account'] = Saccount;
}
}
}
// Создать кнопки, которые будут выводиться в панели инструментов
$buctons[0] = 'view-mailbox';
$buttons[l] = ’new-message';
$buttons[2] = 'account-setup';
// Кнопка выхода из системы нужна, только если был совершен вход
if(check_auth_user())
{
$buttons[4] = 'log-out';
}
/У******ж********х****хх*х****************************************************
// Этап 2. Формирование заголовков
// Отправка HTML-заголовков и строки меню, соответствующих текущему действию
у/*******************х***************х**ж*************************************
if(Saction)
{
Глава 29. Разработка почтовой Web-службы
655
// Вывести заголовок с названием приложения и описанием страницы или действия
do_html_header($_SESSION['auth_user1] ,
"Свежая почта - “.format_action($action),
$_SESSION[1selected_account1]);
}
else
{
// Вывести заголовок с одним лишь названием приложения
do_html_header($_SESSION['auth_user'], "Свежая почта",
$_SESSION['selected_account']);
}
display__toolbar ($buttons) ;
//*********************************★******************************************
II Этап 3. Тело обработчика пользовательских действий
// Отображает соответствующее основное содержимое в зависимости от действия
^****************************************************************************
// Вывести любой текст, сгенерированный функциями,
// которые вызваны до отображения заголовка
echo $status;
if(!check_auth_user())
{
echo '<р>Вы должны сначала войти в систему';
if($action&&$action! = 1 log-out')
echo 1 и затем переходить на '.format_action($action);
echo 1 . </pxbr /xbr />';
display_login—form($action);
}
else
{
switch ( $action )
{
// Если выбрана опция создания новой учетной записи,
// либо учетная запись только что добавлена или удалена,
// отобразить страницу создания учетных записей
case 'store-settings' :
case 'account-setup' :
case 'delete-account' :
{
display_account_setup($_SESSION['auth_user'])
break;
)
case 'send-message1 :
{
if(send_message($to, $cc, $subject, $message))
echo '<р>Сообщение отправлено.</pxbr /xbr /xbr /xbr /xbr /xbr />';
else
echo '<р>Невозможно отправить сообшение.</pxbr /xbr /xbr /xbr />
<br /xbr />';
break;
}
656
Часть V. Реальные проекты на РНР и MySQL
case 'delete' :
{
delete_message($_SESSION['auth_user'],
$-SESSION['selected_account'], $messageid);
// Заметьте, оператор 'break' опущен умышленно —
// мы должны перейти на следующий оператор case
}
case 'select-account' :
case 'view-mailbox' :
{
// Если почтовый ящик только что выбран, отобразить его содержимое
display_list($_SESSION['auth_user'],
$_SESSION['selected_account']);
break;
}
case 'show-headers' :
case 'hide-headers' :
case 'view-message' :
{
// Если только что выбрано сообщение из списка, либо же
// оно просматривается и выбрана опция сокрытия или
// показа заголовков, загрузить сообщение
$fullheaders = ($action=='show-headers’);
display_message($_SESSION['auth_user'],
$_SESSION['selected_account'],
$messageid, $fullheaders);
break;
}
case 'reply-all' :
{
// Установить значение переменной cc равным строке сс текущего сообщения
if(! Simap)
$imap = open_mailbox($_SESSION['auth_user'],
$_SESSION!'selected_account']);
if($imap)
{
$header = imap_header($imap, $messageid);
if($header->reply_toaddress)
$to = $header->reply_toaddress;
else
$to = $header->fromaddress;
$cc = $header->ccaddress;
$subject = 'Re: ’.$header->subject;
$body = add_quoting(imap_body($imap, $messageid));
imap_close($ imap);
display_new_message_form($_SESSION['auth_user'],
$to, $cc, $subject, $body);
}
break;
}
case 'reply' :
{
Глава 29. Разработка почтовой Web-службы
657
// Установить значение переменной to равным полю reply-to
// или from текущего сообщения
if(!$imap)
$imap = open_mailbox($_SESSION['auth_user'],
$_SESSION[1selected_account' ] ) ;
if($imap)
(
$header = imap_header($imap, $messageid);
if($header->reply_toaddress)
$to = $header->reply_toaddress;
else
$to = $header->fromaddress;
$subject = 'Re: ’.$header->subject;
$body = add_quoting(stripslashes(imap_body($imap, Smessageid)));
imap_close($imap);
display_new_message_form($_SESSION['auth_user'],
$to, $cc, $subject, $body);
}
break;
}
case 'forward' :
{
// Установить значение переменной body равным телу
// текущего сообщения, взятого в кавычки
if(!$imap)
$imap = open_mailbox($_SESSION['auth_user'],
S-SESSION['selected-account']);
if($imap)
{
$header = imap_header($imap, $messageid);
$body = add_quoting(stripslashes(imap_body($imap, $messageid)));
$subject = 'Fwd: '.$header->subject;
imap_close($imap);
display_new_message_form($_SESSlON['auth_user'],
$to, $cc, Ssubject, $body);
}
break;
}
case 'new-message' :
{
display_new_message_form($_SESSION['auth_user'],
$to, $cc, $subject, $body);
break;
}
//**********************************************************хх**хж************
// Этап 4. Вывод нижнего колонтитула
//*******************************************★********************************
do_html_footer() ;
658
Часть V. Реальные проекты на РНР и MySQL
В сценарии index.php используется метод обработки событий. Он предполагает
логическую последовательность выполнения функций для каждого события. В дан-
ном случае события инициируются пользователем, который выполняет щелчки на
кнопках страницы. Большинство кнопок генерируется функцией display_button(),
а функция display_form_button () применяется для вывода кнопок отправки формы.
Обе функции содержатся в библиотеке output_fns .php. Все они обеспечивают пере-
ход по следующему URL-адресу:
index.php?action=log-out
Когда вызывается сценарий index.php, значение переменной action определяет,
обработчик какого события должен быть запущен.
Рассмотрим четыре основных раздела сценария index. php.
1. Выполняются некоторые операции, которые должны предшествовать отправ-
ке заголовка страницы в браузер. Сюда относятся: запуск сеанса, выполнение
предварительной обработки для выбранного пользователем действия и опре-
деление внешнего вида заголовков.
2. Обрабатываются и отправляются необходимые заголовки и строка меню для
выбранного пользователем действия.
3. Выбирается фрагмент сценария для выполнения в зависимости от предпри-
нятого действия. Различные действия приводят к запуску различных функций.
4. Отправляются нижние колонтитулы страницы.
Как видно в листинге, эти четыре раздела помечены соответствующими коммен-
тариями.
Для полного понимания сценария давайте последовательно рассмотрим все дей-
ствия, реализуемые на сайте.
Вход и выход из системы
Когда пользователь загружает страницу index.php, генерируется вывод, показан-
ный на рис. 29.2.
Это стандартное поведение приложения. Когда ни одно действие не выбрано
(и, стало быть, значение переменной $action не установлено) и не предоставлены
данные для входа в систему, необходимо выполнить приведенные ниже фрагменты
кода.
На этапе предварительных операций выполняется следующий код:
include ('include_fns.php’);
session_start();
Эти строки запускают сеанс, который будет использоваться для отслеживания пе-
ременных сеанса $auth_user и $selected_account. К этим переменным мы вернемся
чуть позже.
Как и в ранее разработанных приложениях, мы создаем короткие имена перемен-
ных. Мы проделывали это в каждом сценарии обработки формы, начиная с первой
главы книги, поэтому нет никакого повода не предпринимать подобные действия и в
отношении переменной action. В зависимости от того, где это происходит, упомяну-
Глава 29. Разработка почтовой Web-службы
659
тая переменная может быть либо GET-, либо POST-переменной. Затем ее значение из-
влекается из массива $_REQUEST. Те же действия должны производиться и с перемен-
ной account, поскольку доступ к ней обычно осуществляется с помощью метода GET,
однако когда учетная запись удаляется, то доступ к этой переменной выполняется
через метод POST.
Рис. 29.2. Окно входа в систему для приложения “Свежая почта”
запрашивает имя пользователя и пароль
Чтобы упростить настройку пользовательского интерфейса, отображаемые в
панели инструментов кнопки управляются массивом. Сначала объявляется пустой
массив:
$buttons = array();
Затем устанавливаются кнопки, которые должны отображаться на странице:
$buttons[0] = ’view-mailbox';
$buttons[l] = 'new-message’;
$buttons[2] = 'account-setup';
Если позже пользователь будет входить как администратор, в этот массив будут
добавлены дополнительные кнопки.
На втором этапе создается простой заголовок:
do_html_header($_SESSION['auth_user'], 'Свежая почта',
$_SESSION['selected_account' ]);
display_toolbar($buttons);
Этот код выводит строку заголовка и панель инструментов с кнопками, как пока-
зано на рис. 29.2. Задействованные в приведенном фрагменте кода функции содер-
жатся в библиотеке output_fns . php. Поскольку результат действия функций хорошо
виден на иллюстрации, мы их рассматривать не будем.
660
Часть V. Реальные проекты на РНР и MySQL
Давайте теперь перейдем к основной части кода:
if(!check_auth_user())
{
echo '<р>Вы должны сначала войти в систему';
if($action&&$action!='log-out')
echo ' и затем переходить на '.format_action($action);
echo ' ,</pxbr /><Ьг />' ;
display_login_form($action);
}
Функция check_auth_user() входит в состав библиотеки user_auth_fns.php. В
предыдущих проектах применялся очень похожий код для проверки, вошел ли поль-
зователь в систему. Если вход не был совершен, как в данном случае, отображается
форма входной регистрации, которую можно увидеть на рис. 29.2. Эта форма генери-
руется функцией display_login_f orm () из библиотеки output_fns .php.
Если пользователь правильно заполнил форму и щелкнул на кнопке Log In (Вход),
отображается экран, показанный на рис. 29.3.
Рис. 29.3. После успешного входа в систему пользователь может
приступать к работе с приложением
При таком выполнении рассматриваемого сценария активизируются различные
разделы кода. Форма входной регистрации содержит два поля, $username и $pass-
word. Если они оба заполнены, активизируется следующий фрагмент кода, относя-
щийся к предварительной обработке:
if($username || $password)
{
if(login($username, $passwd))
{
$status .= '<р>Вы успешно вошли в систему.</р><Ьг /><Ьг /><Ьг /><Ьг />
<br /><Ьг />';
Глава 29. Разработка почтовой Web-службы
661
$_SESSION['auth_user'] = $username;
if(number_of_accounts($_SESSION['auth_user']) == 1)
{
$accounts = get_account_list($_SESSION['auth_user']);
$_SESSION['selected_account'] = $accounts[0];
}
)
else
{
$status .= '<р>Извините, вход в систему с данным именем пользователя
и паролем невозможен.</pxbr /><Ьг /><Ьг /><Ьг /><Ьг /><Ьг />';
)
)
Как видите, здесь вызывается функция login (), подобная той, что использовалась
в главах 26 и 27. Если все прошло успешно, имя пользователя регистрируется в пере-
менной сеанса auth_user.
Помимо кнопок, отображаемых до входа в систему, мы добавили кнопку’, которая
дает пользователю возможность снова выйти из системы:
if(check_auth_user())
{
$buttons[4] = 'log-out';
}
На рис. 29.3 легко заметить эту кнопку.
На втором этапе, который связан с формированием заголовков, снова отобража-
ются заголовок и кнопки. В теле сценария выводится сообщение состояния, создан-
ное ранее:
echo $status;
После этого остается только вывести нижний колонтитул и ожидать последующих
действий пользователя.
Настройка учетных записей
Когда пользователь впервые начинает работать с системой “Свежая почта”, он
должен настроить одну или несколько учетных записей электронной почты. После
щелчка на кнопке Account Setup (Настройка учетной записи) переменная action
получает значение account-setup и производится повторный вызов сценария
index .php. Отображаемый в результате этого вывод показан на рис. 29.4.
Вернемся к сценарию из листинга 29.2. На этот раз значение переменной $action
определяет другое поведение.
Заголовок создается с незначительными отличиями:
do_html_header($_SESSION['auth_user'],
'Свежая почта - '.format_action($action),
$_SESSION['selected_account']);
Гораздо важнее отличия в теле страницы:
case 'store-settings' :
case 'account-setup' :
662
Часть V. Реальные проекты на РНР и MySQL
case 'delete-account' :
{
display_account_setup($_SESSION['auth_user']);
break;
}
Это довольно-таки типичный шаблон — каждая команда вызывает некоторую
функцию. В данном случае вызывается функция display_account_setup (), код кото-
рой можно найти в листинге 29.3.
Рис. 29.4. Прежде чем можно будет работать с электронной
почтой, пользователь должен настроить свою учетную запись
Листинг 29.3. Функция display_account_setup() из библиотеки output_fns.php —
эта функция осуществляет прием и отображение данных учетной записи
function display_account_setup($auth_user)
{
I/ Выводит форму для определения новой учетной записи
display_account_form. ($auth_user) ;
$list = get_accounts($auth_user);
$accounts = sizeof($list);
// Отобразить все сохраненные учетные записи
foreach($list as $key => $account)
{
// Для каждой учетной записи вывести форму для представления
// детальной информации. Обратите внимание, что мы отправляем пароли всех
// учетных записей в виде простого HTML-кода. Это не самая лучшая идея.
display_account_form($auth_user, $account['accountid'], $account['server'],
$account['remoteuser'], $account['remotepassword'],
Saccount['type'], $account['port']);
}
}
Глава 29. Разработка почтовой Web-службы
663
При вызове функция display_account.setup () выводит пустую форму для добав-
ления новой учетной записи, за которой следуют редактируемые формы, содержащие
данные всех учетных записей пользователя. Функция display_account_form() ото-
бражает форму, показанную на рис. 29.4. Несложно заметить, что функция использу-
ется двумя способами: при ее вызове без параметров отображается пустая форма, а
при вызове с полным набором параметров выводится существующая запись. Эта
функция входит в состав библиотеки output.fns .php. Она просто выводит HTML-
содержимое, поэтому здесь не рассматривается.
Для извлечения существующих учетных записей служит функция get_accounts ()
из библиотеки mail_fns .php. Ее код можно найти в листинге 29.4.
Листинг 29.4. Функция get_accounts () из библиотеки mail_fns .php — эта функция
извлекает данные всех учетных записей для определенного пользователя
function get_accounts($auth_user)
{
$list = array!);
if($conn->db_connect())
{
Squery = "select * from accounts where username = 1$auth_user'";
Sresult = $conn->query(Squery);
if(Sresult)
{
while(Ssettings = $result->fetch_assoc())
array_push($list, Ssettings);
)
else
return false;
)
return $list;
)
Функция get_accounts () подключается к базе данных, извлекает все учетные за-
писи для определенного пользователя и возвращает их в виде массива.
Создание новой учетной записи
Когда пользователь заполняет форму учетной записи и выполняет щелчок на кноп-
ке Save Changes (Сохранить изменения), активизируется действие store-settings.
Давайте рассмотрим код обработки этого события в сценарии index.php. На этапе
предварительной обработки выполняется следующий код:
case 'store-settings' :
{
store_account_settings($_SESSION['auth_user'], $_POST);
break;
}
Функция store_account_settings () сохраняет данные новой учетной записи в
базе данных. Код этой функции показан в листинге 29.5.
664
Часть V. Реальные проекты на РНР и MySQL
Листинг 29.5. Функция store_ account-Settings () из библиотеки mail_fns .php —
эта функция сохраняет для пользователя данные по новой учетной записи
function store_account_settings($auth_user, Ssettings)
I
if(!filled_out(Ssettings))
(
echo 'Все поля должны быть заполнены. Повторите попытку.<br /><Ьг />';
return false;
}
else
{
if(Ssettings['account']>0)
Squery = "update accounts set server = 'Ssettings[server]',
port = Ssettings[port], type = 'Ssettings[type]',
remoteuser = 'Ssettings[remoteuser]1,
remotepassword = 1Ssettings[remotepassword]'
where accountid = Ssettings[account]
and username = '$auth_user'";
else
Squery = "insert into accounts values ('$auth_user1,
'Ssettings[server]1, Ssettings[port],
'Ssettings[type]', ’Ssettings[remoteuser)',
'Ssettings[remotepassword]', NULL)";
if(Sconn->db_connect())
{
Sresult = $conn->query(Squery);
if (Squery)
return true;
else
return false;
}
else
{
echo 'Невозможно сохранить изменения.<br /xbr /xbr /xbr /xbr /xbr />';
return false;
}
}
}
Функция реализует две опции — ввод новой учетной записи и обновление сущест-
вующей. Она выполняет запрос на сохранение данных учетной записи.
После сохранения данных учетной записи осуществляется возврат к этапу вывода
тела основной страницы в сценарии index.php:
case 'store-settings' :
case 'account-setup' :
case 'delete-account' :
{
display_account_setup($-SESSION['auth_user']);
break;
J
Глава 29. Разработка почтовой Web-службы
665
Затем, как и ранее, выполняется вызов функции display_account_setup() для
вывода списка данных по учетной записи пользователя. Вновь созданная учетная за-
пись уже должна быть включена.
Изменение существующей учетной записи
Процесс изменения учетной записи во многом подобен описанному выше. Поль-
зователь может изменить данные и щелкнуть на кнопке Save Changes (Сохранить
изменения). Снова активизируется действие store-settings, однако на сей раз про-
изойдет обновление данных учетной записи, а не добавление новой записи в базу
данных.
Удаление учетной записи
Для этого необходимо щелкнуть на кнопке Delete Account (Удалить учетную за-
пись), которая отображается под каждым списком учетных записей. В результате ак-
тивизируется действие delete-account.
В начальном разделе сценария index. php выполняется следующий код:
case 'delete-account' :
{
delete_account($_SESSION['auth_user'], $account);
break;
}
В этом коде вызывается функция delete_account (), приведенная в листинге 29.6.
Удаление учетных записей необходимо выполнять до обработки заголовка, поскольку
именно внутри заголовка предоставляется выбор учетной записи для использования.
Список учетных записей должен быть обновлен для корректного отображения.
Листинг 29.6. Функция delete account () из библиотеки mail_fns .php —
эта функция удаляет данные одной учетной записи
function delete_account($auth_user, $accountid)
{
// Удаляет из базы данных одну учетную запись для данного пользователя
Squery = "delete from accounts where accountid='$accountid' "
."and username ='$auth_user';
if(db_connect())
{
$result = $conn->query(Squery);
J
return Sresult;
j
После выполнения функции происходит возврат к разделу сценария index.php,
который связан с формированием тела страницы. При этом выполняется следующий
код:
case 'store-settings' :
case 'account-setup' :
case 'delete-account' :
666
Часть V. Реальные проекты на РНР и MySQL
(
display_account_setup($_SESSION[1auth_user']);
break;
}
Вы наверняка заметили, что это тот же самый код, который выполнялся ранее —
он просто отображает список учетных записей пользователя.
Чтение почтовых сообщений
После того как пользователь настроит несколько учетных записей, можно пере-
ходить к главному — подключению к учетным записям почтового сервера и собствен-
но чтению почты.
Выбор учетной записи
Для чтения почты должна быть выбрана одна из учетных записей. Текущий выбор
сохраняется в переменной сеанса $selected_account.
Если пользователь зарегистрировал в системе единственную учетную запись, она
будет автоматически выбираться при входе в систему:
if(number_of_accounts($_SESSION['auth_user'])==1)
{
$accounts = get_account_list(S_SESSION['auth_user']);
$_SESSION['selected_account'] = Saccounts[0];
)
Функция number_of_accounts () из библиотеки mail_fns .php служит для выявле-
ния случаев, когда с пользователем связано более одной учетной записи (код этой
функции можно найти в листинге 29.7). Функция get_account_list () извлекает мас-
сив имен учетных записей пользователя. В данном случае запись является единствен-
ной. Ее можно извлечь как значение массива, хранящееся в элементе с индексом 0.
Листинг 29.7. Функция number_of_асcounts () из библиотеки xnail_fns .php — эта
функция вычисляет, сколько учетных записей было зарегистрировано пользователем
function number_of_accounts($auth_user)
{
// Определяет количество учетных записей, принадлежащих данному пользователю
Squery = "select count(*) from accounts where username = 1$auth_user'";
if(db_connect())
{
Sresult = $conn->query(Squery);
if(Sresult)
{
$row = $result->fetch_array();
return $row[0];
)
return 0;
}
Г лава 29. Разработка почтовой Web-службы
667
Функция get_account_list() подобна рассмотренной ранее функции
get_accouncs () за исключением того, что извлекаются только имена учетных
записей.
Если пользователь зарегистрировал несколько учетных записей, потребуется вы-
брать одну из них для использования. В этом случае заголовки будут содержать список
<select>, в котором перечислены доступные учетные записи. При выборе одной из
них автоматически отображается соответствующий почтовый ящик, как показано на
рис. 29.5.
Свежая почта - View Mafcox - МозИа<: -4-1 я
Файл Правка Вид Переход Закладки Инструменты ttw Справка Detoyg QA
’’а а ........... . ................. — -..-
® ж ® > 4 http://localhostA]hpmysql3e/thapter29/indexJ3hp’action=v
: назад Обновить
Г • I * ’ Г; ’ 'с г
j VipwMoribox | Message | Account Setup ! teg Out
N1 )25-Jun-2005 Youry Artemenko Test message for chapter (2288 cnars)
N 2)25-Jun-2005 Youry Artemenko Test message for chapter (2310 chars)
N 3)25-Jun-20051roun/ArtemenkoTest message for chapter (2304 chars)
Рис. 29.5. После выбора в списке <select> учетной записи загружа-
ется и отображается содержимое почтового ящика, который соот-
ветствует этой учетной записи
Эта опция <select> генерируется в рамках функции do_html_header (), входящей
в состав библиотеки output_fns .php, как показано в следующем фрагменте кода:
// Включать поле со списком выбора учетных записей, только если
// с данным пользователем связано более одной учетной записи
if (number__of_accounts ($auth_user) >1)
(
echo ’<form target="index.php?action=open-mailbox" method="post">1;
echo ’<td bgcolor="#ff6600" align="right" valign="middle">’;
display_account_select($auth_user, $selected_account);
echo ’<Itd>1;
echo ’</form>';
}
Обычно мы избегаем обсуждения HTML-кода, используемого в примерах данной
книги. Однако для HTML-кода, генерируемого функцией display_account_select (),
было решено сделать исключение.
668
Часть V. Реальные проекты на РНР и MySQL
В зависимости от учетных записей текущего пользователя, функция
display_account_select () генерирует HTML-код наподобие следующего:
<select onchange="window.location=this.options[selectedlndex].value”
name="account">
<option value="0'' selected>
Выберите учетную запись</а>
<option value="index.php?action=select-account&account=10">
mail.domai n.com
</option>
<option value="index.php?action---select-account&account=11">
mail.server.com
</option>
<option value=”index.php?action=select-account&account=9">
localhost
</option>
</select>
Большая часть приведенного выше кода создает HTML-элемент <select>, однако в
коде также содержится небольшой сценарий на языке JavaScript. Подобно тому, как
РНР применяется для генерации HTML-содержимого, JavaScript можно использовать
для создания сценариев, выполняемых на стороне клиента.
Как только списку поступает событие изменения, JavaScript-сценарий устанавли-
вает значение свойства window. location равным значению опции. Если пользова-
тель выбирает первую опцию списка <select>, свойство window, location примет
значение ' index.php?action=select-account&account=10 '. В результате будет за-
гружен ресурс, находящийся по данному URL-адресу. Очевидно, что если браузер
пользователя не поддерживает JavaScript либо выполнение JavaScript-сценариев от-
ключено, этот код не приведет к желаемым результатам.
Функция display_account_select() из библиотеки output_fns.php служит для
извлечения и отображения списка доступных учетных записей. Кроме того, она вы-
зывает рассмотренную ранее функцию get_account_list ().
В результате выбора одной из опций списка <select> активизируется событие
select_account. Как видно на рис. 29.5, значение свойства window, location при-
соединено к концу URL-адреса вместе с идентификатором выбранной учетной
записи.
Добавление этих GET-переменных имеет двойной эффект. Во-первых, на этапе
предварительной обработки сценария index. php выбранная учетная запись сохраня-
ется в переменной сеанса $selected_account, как показано ниже:
case 'select-account' :
{
// Если выбрана допустимая учетная запись,
// сохранить ее в переменной сеанса
if($account&&account_exists($_SESSION['auth_user'], $account))
{
$_SESSION[’selected_account'] = $account;
}
} >
Глава 29. Разработка почтовой Web-службы
669
Во-вторых, когда выполняется этап построения тела страницы, задействуется сле-
дующий код:
case 'select-account' :
case 'view-mailbox' :
{
// Если почтовый ящик только что выбран, отобразить его содержимое
display_list($_SESSION['auth_user'],
$_SESSION['selected_account']);
break;
}
Как видите, предпринимаются те же действия, как если бы пользователь выбрал
опцию View Mailbox (Просмотреть ящик), которая рассматривается в следующем
разделе.
Просмотр содержимого почтового ящика
Содержимое почтового ящика можно просмотреть с помощью функции
display_list (). Она отображает список всех сообщений, которые находятся в поч-
товом ящике. Код этой функции показан в листинге 29.8.
Листинг 29.8. Функция display_list () из библиотеки output_fns .php — эта функция
отображает все сообщения, находящиеся в почтовом ящике
function display_list($auth_user, $accountid)
{
// Отображает список сообщений, находящихся в данном почтовом ящике
global $table_width;
if(!$accountid)
{
echo 'Почтовый ящик не выбран<Ьг /xbr /xbr /xbr /xbr /xbr />.';
)
else
{
$imap = open_mailbox($auth_user, $accountid);
if(Simap)
{
echo "<table width=$table_width' cellspacing='0'
cellpadding='6' border='0'>";
$headers = imap_headers($imap);
11 Мы можем переформатировать эти данные либо получить другую информацию
// с помощью imap_fetchheaders, однако и такой отчет неплох, поэтому
// просто воспользуемся оператором echo
$messages = sizeof($headers);
for($i = 0; $i<$messages; $i++)
{
echo ' ctrxtd bgcolor = " ' ;
if($i%2)
echo '#ffffff',-
else
echo '#ffffcc';
670
Часть V. Реальные проекты на РНР и MySQL
echo '"><a href="index.php?action=view-message&messageid='.($i+l).';
echo $headers[$i];
echo "</a></td></tr>\n";
}
echo '</table>';
else
$account = get_account_settings($auth_user, Saccountid);
echo 'Невозможно открыть почтовый ящик $account['server'].
' .<br /xbr /xbr /xbr />';
}
}
В функции display-list () мы начали пользоваться IMAP-функциями РНР. Два
ключевых момента состоят в открытии почтового ящика и чтении заголовков сооб-
щений.
Почтовый ящик для пользовательской учетной записи открывается с помощью
функции open_mailbox () из библиотеки mail_fns .php. Ее код можно найти в лис-
тинге 29.9.
Листинг 29.9. Функция open_nailbox () из библиотеки nail_fns .php — эта функция
подключается к почтовому ящику пользователя
function open_mailbox($auth_user, $accountid)
f
// Выбрать почтовый ящик, если он единственный
if(number_of-accounts($auth_user)==1)
{
$accounts = get_account_list($auth_user);
$_SESSION['selected_account'] = $accounts[0];
$accountid = Saccounts[0];
}
// Подключиться к POP3- или IMAP-серверу, выбранному пользователем
Ssettings = get_account_settings($auth_user, Saccountid);
if(!sizeof(Ssettings)) return 0;
Smailbox = Ssettings[server];
if(Ssettings[type]=='POP3')
Smailbox .= '/рорЗ';
Smailbox .= Ssettings[port].'JINBOX';
// Подавить вывод предупреждающих сообщений,
// не забыть проверить возвращаемое значение
@ $imap = imap_open(Smailbox, Ssettings['remoteuser'],
Ssettings['remotepassword']);
return $imap;
Почтовый ящик открывается с помощью функции imap_open (). Прототип это
функции выглядит следующим образом:
Глава 29. Разработка почтовой Web-службы
671
int imap_open (string mailbox, string username,
string password [, int options'. )
Ниже перечислены параметры, которые должны быть переданы функции:
mailbox — эта строка должна содержать имя сервера и имя почтового ящика, а
также, необязательно, номер порта и название протокола. Данная строка име-
ет следующий формат:
(имя_хоста/протокол:порт}ящик
Если протокол не указан, по умолчанию принимается IMAP. Из рассмотренно-
го выше фрагмента кода видно, что когда пользователь выбирает для опреде-
ленной учетной записи протокол POP3, именно этот протокол и задается.
Например, чтобы прочитать почту с локального компьютера с использованием
стандартных портов, для IMAP применяется следующее имя почтового ящика:
{localhost:143}INBOX
а для POP3 — такое имя:
(localhost/рорЗ:110}INBOX
username — имя пользователя для доступа к учетной записи.
password — пароль для доступа к учетной записи.
Кроме того, допускается передача необязательных флагов для указания опций,
таких как "open mailbox in read-only mode" (открыть почтовый ящик в режиме
только для чтения).
Обратите внимание, что строка mailbox до передачи в функцию imap_open() со-
ставляется фрагмент за фрагментом с помощью операции конкатенации. Компоно-
вать строку' следует очень внимательно, поскольку подстроки, содержащие символы
{$, могут приводить к ряду проблем в РНР.
В результате вызова функции возвращается IMAP-поток, если почтовый ящик мо-
жет быть открыт, и значение false в противном случае.
После завершения работы с потоком IMAP его можно закрыть с помощью функ-
ции imap_close(imap-поток).
В рассматриваемой функции IMAP-поток передается обратно в главную програм-
му. Затем вызывается функция imap_headers (), которая извлекает заголовки почто-
вых сообщений с целью их отображения:
$headers = imap_headers($imap);
Эта функция возвращает информацию о заголовках для всех сообщений почтово-
го ящика, к которому было выполнено подключение. Информация возвращается в
форме массива, где каждая строка соответствует сообщению. Сам вывод не формати-
руется. Сообщения отображаются построчно, как показано на рис. 29.5.
Можно вывести более полную информацию о заголовках с помощью функции,
имя которой легко спутать с именем предыдущей функции — imap_header (). Тем не
менее, в нашем конкретном случае функция imap_headers ,, выводит вполне доста-
точные сведения.
672
Часть V. Реальные проекты на РНР и MySQL
Чтение почтовых сообщений
Каждая строка, выводимая функцией display_list (), представляет собой ссылку
на определенное сообщение электронной почты.
Вот как выглядит стандартная ссылка на сообщение:
index.php?action=view-message&messageid=6
Здесь messageid представляет собой числовую последовательность, которая ис-
пользовалась в извлеченных ранее заголовках. Обратите внимание, что нумерация
IMAI’-сообщепий начинается с единицы, а не с нуля.
После щелчка на одной из этих ссылок генерируется вывод, который должен быть
похож на показанный на рис. 29.6.
Рис. 29.6. С помощью действия view-message выводится опре-
деленное сообщение, которое в данном случае является спамом
Когда данные параметры передаются в сценарий index.php, выполняется сле-
дующий фрагмент кода:
case 'show-headers' :
case 'hide-headers' :
case 'view-message' :
(
// Если только что выбрано сообщение из списка, либо же
// оно просматривается и выбрана опция сокрытия или
// показа заголовков, загрузить сообщение
$fullheaders = ($action=='show-headers');
display_message($_SESSION['auth_user'],
$_SESSION[1selected_account'],
$messageid, $fullheaders);
break;
}
Глава 29. Разработка почтовой Web-службы
673
Здесь значение переменной action проверяется на предмет равенства строке
'show-headers'. В данном случае проверка дает результат false, и значение пере-
менной $fullheaders также устанавливается равным false. Действие show-headers
рассматривается несколько позже.
Следующую строку:
$fullheaders = (Saction=='show-headers');
можно заменить не столь лаконичным, но, возможно, более понятным вариантом:
if($action=='show-headers')
$fullheaders = true;
else
$fullheaders = false;
Затем следует вызов функции display_message (). Эта функция, в основном, реа-
лизует простой вывод HTML-содержимого, поэтому ее код здесь не рассматривается.
Она обращается к функции retrieve_message () для извлечения требуемого сообще-
ния из почтового ящика:
Smessage = retrieve_message($auth_user, Saccountid, Smessageid, Sfullheaders);
Функция retrieve_message () входит в состав библиотеки mail_fns .php. Ее код
можно найти в листинге 29.10.
Листинг 29.10. Функция retrieve_message () из библиотекиmail_fns.php —
эта функция извлекает определенное сообщение из почтового ящика
function retrieve_message($auth_user, $accountid, Smessageid, $fullheaders)
{
$message = arrayO;
if(I($auth_user && Smessageid && Saccountid))
return false;
$imap = open_mailbox($auth_user, Saccountid);
if(!Simap)
return false;
Sheader = imap_header($imap, Smessageid);
if(!Sheader)
return false;
Smessage!'body'] = imap_body($imap, Smessageid);
if(!Smessage['body'])
Smessage['body'] = "[Отсутствует тело сообщения]\n\n\n\n\n\n";
if(Sfullheaders)
Smessage['fullheaders'] = imap_fetchheader(Simap, Smessageid);
else
Smessage['fullheaders'] = ' ' ;
Smessage['subject'] = Sheader->subject;
Smessage['fromaddress'] = $header->fromaddress;
Smessage['toaddress'] = $header->toaddress;
Smessage['ccaddress'] = $header->ccaddress;
Smessage['date'] = $header->date;
// Следует отметить, что можно получить более подробную информацию,
// если вместо полей fromaddress и toaddress воспользоваться
674
Часть V. Реальные проекты на РНР и MySQL
// полями from и по, однако мы выбрали именно такой вариант ввиду его простоты
imap_close($imap);
return $message;
}
И снова для открытия почтового ящика используется функция open_mailbox ().
Однако на этот раз требуется получить определенное сообщение. С помощью этой
библиотеки функций мы можем загрузить заголовки и тело сообщения по отдельности.
Здесь мы прибегли к услугам трех IMAP-функций — imap_header () ,
imap_f etchheader () и imap_body(). Обратите внимание на отличия двух первых
функций от использованной ранее imap_headers (). Их названия довольно-таки легко
спутать. Ниже приводится краткая информация по функциям:
imap—headers () — возвращает список заголовков всех сообщений в почтовом
ящике в виде массива, причем каждому сообщению соответствует один элемент.
imap_header () — возвращает заголовки определенного сообщения в виде объекта.
imap_fetchheader () — возвращает заголовки определенного сообщения в виде
строки.
В нашем конкретном случае функция imap_header () используется для заполнения
полей определенного заголовка, a imap_fetchheader () — для вывода всех заголовков,
если это затребовано. (Немного позже мы еще вернемся к этой теме.)
Функции imap_header () и imap_body () служат для создания массива, содержаще-
го все элементы интересующего сообщения.
Функция imap_header () вызывается следующим образом:
$header = imap_header($imap, Smessageid);
После этого можно извлечь из объекта все необходимые поля:
$mes sage['subj ec t'] = Sheader-> subj ec t;
Для добавления тела сообщения в массив применяется функция imap_body ():
Smessage['body'] = imap_body($imap, $messageid);
И, наконец, с помощью функции imap_close () закрывается почтовый ящик
и возвращается сформированный массив. Затем с использованием функции
display_message () можно вывести поля сообщения в виде, показанном на рис. 29.6.
Просмотр заголовков сообщений
На рис. 26.7 можно заметить кнопку Show Headers (Показать заголовки). Она ак-
тивизирует действие show-headers, которое приводит к выводу всех заголовков в
процессе отображения сообщения. Генерируемый в результате щелчка на этой кноп-
ке вывод можно увидеть на рис. 29.7.
Возможно, вы заметили, что обработка события view-message охватывает
также действие show-headers (и его дополнение — hide-headers). Когда эта оп-
ция выбирается, выполняются те же операции, что и ранее. Однако в функции
retrieve_message () осуществляется дополнительный вывод полного текста заго-
ловков:
Глава 29. Разработка почтовой Web-службы
675
if ($fullheaders)
Smessage['fullheaders'] = imap_fetchheader($imap, $messageid);
Затем эти заголовки можно вывести пользователю.
Рис. 29.7. Действие show-headers, имеющее целью отобразить все
заголовки определенного сообщения, помогает пользователю от-
слеживать происхождение спама
Удаление почтовых сообщении
После щелчка на кнопке Delete (Удалить) во время отображения определенного
сообщения активизируется действие delete. В результате выполняется следующий
фрагмент кода сценария index.php;
case 'delete' :
{
delete_message($_SESS!ON['auth_user';,
$_SESSION['selected_account'], $messageid);
// Заметьте, оператор 'break' опушен умышленно —
// мы должны перейти на следующий оператор case
}
case 'select-account' :
case 'view-mailbox' :
{
// Если почтовый ящик только что выбран, отобразить его содержимое
display_list($_SESSION['auth_user'],
$_SESSION['selected_account']);
break;
}
676
Часть V. Реальные проекты на РНР и MySQL
Сначала с помощью функции delete_message () сообщение удаляется, а затем ото-
бражается обновленное содержимое почтового ящика, как описывалось ранее. Код
функции delete_message () можно найти в листинге 29.11.
Листинг 29.11. Функция delete_message () из библиотеки ns .php — эта функция
удаляет из почтового ящика определенное сообщение
function delete_message($auth_user, Saccountid, $message_id)
(
// Удаляет на сервере одно сообщение
Simap = open_mailbox($auth_user, Saccountid);
if(Simap)
{
imap_delete(Simap, $message_id);
imap_expunge(Simap);
imap_close(Simap);
return true;
}
return false;
}
Здесь мы вызываем множество IMAP-функций. К числу новых из них можно
отнести imap_delete () и imap_expunge (). Обратите внимание, что функция
imap_delete () только помечает сообщения как удаленные. Можно помечать любое
количество сообщений. В результате вызова функции imap_expunge () сообщения
действительно удаляются.
Отправка почты
Наконец-то мы можем вплотную заняться отправкой сообщений электронной
почты. Для этого наш сценарий предоставляет несколько способов: пользователь
может отправить новое сообщение, ответить на сообщение, а также переадресовать
его. Рассмотрим последовательно реализацию всех перечисленных возможностей.
Отправка нового сообщения
Пользователь может выбрать отправку нового сообщения, щелкнув на кнопке
New Message (Новое сообщение). В результате будет активизировано действие
new-message, которое приводит к выполнению следующего фрагмента кода из сцена-
рия index.php:
case 'new-message' :
{
display_new_message_form($_SESSION['auth_user'],
$to, See, Ssubject, $body);
break;
}
Форма для создания нового сообщения представляет собой типовую форму от-
правки сообщения. Ее вид показан на рис. 29.8. В действительности на рисунке пока-
зан режим переадресации, тем не менее, форма используется та же.
Глава 29. Разработка почтовой Web-службы
677
га Свежая почта • forward - Morila [cXAi L«5 IfcjvtUJ'XXF
жвшммв
Файл Правка Вид Переход Закладки Инструменты Оно gf^sasra Debyg QA
Назад
Рис. 29.8. Используя механизм переадресации, можно заявить на
отправителя спама
В результате щелчка на кнопке Send Message (Отправить сообщение) иницииру-
ется действие send-message, которое влечет за собой выполнение следующего кода:
case 1send-message1 :
{
if(send_message(Sto, See. Ssubject, Smessage))
echo ’ <р>Сообщение отправлено.</pxbr /xbr /xbr /xbr /xbr xbr />';
else
echo 1<р>Невозможно отправить сообщение. </pxbr /xbr /xbr 'xbr />
<br /xbr />’;
break;
}
Здесь вызывается функция send_message (), которая осуществляет отправку со-
общения. Ее код приведен в листинге 29.12.
Листинг 29.12. Функция send message () из библиотекиmail fns.php — эта функция
отправляет введенное пользователем сообщение
function send_message($to, Sec, Ssubject. Smessage)
(
// Отправляет одно сообщение электронной почты с помощью РНР
if (!$conn->db_connect())
{
return false;
)
Squery = 'select address from users where '
.'username=\''.$_SESSION['auth_user']....;
Sresult = $conn->query(Squery);
678
Часть V. Реальные проекты на РНР и MySQL
if (!Sresult)
{
return false;
)
else if ($result->num_rows==0)
{
return false;
)
else
{
$row = $result->fetch_object();
Sother = 'From: '.$row->address;
if (!empty($cc))
Sother.="\r\nCc: See";
if (mail($to, Ssubject, Smessage, Sother))
{
return true;
)
else
{
return false;
}
}
Как видите, здесь для отправки сообщения электронной почты используется
функция mail(). Однако сначала выполняется загрузка адреса электронной почты
пользователя из базы данных. Этот адрес будет помещен в поле “From” (“От”).
Ответ или переадресация сообщения
Опции Reply (Ответить), Reply All (Ответить всем) и Forward (Переслать) выпол-
няют отправку сообщений таким же образом, как и опция New Message. Отличия
заключаются в том, что они частично заполняют форм)’ сообщения перед ее выводом
для пользователя. Давайте-ка вернемся к рис. 29.8. Текст пересылаемого сообщения
выводится с отступом. Его строки начинаются с символа >. В начало строки Subject
(Тема) помещается выполняемое действие, то есть Re: (от “Replay” — “Ответить”).
Опции Reply и Reply АП обеспечивают заполнение полей адреса получателя и темы, а
также выводят текст предыдущего сообщения с отступом аналогичным образом.
Все сказанное выше реализовано в коде третьего раздела сценария index. php:
case 'reply-all' :
{
//Установить значение переменной сс равным строке сс текущего сообщения
if(!$imap)
Simap = open_mailbox($_SESSION[’auth_user'],
$_SESSION['selected_account']);
if($imap)
{
Sheader = imap_header($imap, Smessageid);
if($header->reply_toaddress)
$to = $header->reply_toaddress;
Глава 29. Разработка почтовой Web-службы
679
else
Sto = $header->fromaddress;
$cc = $header->ccaddress;
Ssubject = 'Re: '.$header->subject;
$body = add_quoting(stripslashes(imap_body($imap, Smessageid)));
imap_c1оse($ imap);
display_new_message_fonn($_SESSION[’auth_user1],
$to, $cc, Ssubject, $body);
}
break;
)
case 1 reply' :
(
// Установить значение переменной to равным полю reply-to
I/ или from текущего сообщения
if(!Simap)
$imap = open_mailbox($_SESSION['auth_user'],
$_SESSION['selected_account']);
if(Simap)
(
Sheader = imap_header($imap, Smessageid);
if($header->reply_toaddress)
$to = $header->reply_toaddress;
else
$to = $header->fromaddress;
Ssubject = 'Re: 1.$header->subject;
$body = add_quoting(stripslashes(imap_body(Simap, Smessageid)));
imap_close(Simap);
display_new_message_form($_SESSION['auth_user'],
Sto, See, Ssubject, $body) ,-
}
break;
)
case 'forward' :
{
// Установить значение переменной body равным телу текущего сообщения,
// взятого в кавычки
if(!Simap)
Simap = open_mailbox($_SESSION['auth_user'],
$_SESSION['selected_account' ]) ;
if(Simap)
{
Sheader = imap_header($imap, Smessageid);
$body = add_quoting(stripslashes(imap_body(Simap, Smessageid)));
Ssubject = 'Fwd: '.$header->subject;
imap_close($imap);
display_new_message_form($_SESSION['auth_user'],
$to, $cc, Ssubject, $body);
}
break;
680
Часть V. Реальные проекты на РНР и MySQL
Каждая из рассмотренных опций создает соответствующие заголовки, реализует
необходимое форматирование и вызывает функцию display_new_message () для вы-
вода формы.
Ну вот, на этом-то и исчерпывается набор функциональных возможностей Web-
приложения управления электронной почтой.
Расширение проекта
Как и в подавляющем большинстве учебных проектов, существует множество пу-
тей расширения и совершенствования проекта. За образец можно взять используе-
мую вами программу чтения электронной почты. Ниже перечислены некоторые по-
лезные расширения проекта:
Предусмотрите возможность регистрации пользователей на сайте. (Для
этого можно повторно использовать некоторые фрагменты кода, приведенно-
го в главе 26.)
Добавьте возможность пользователям иметь несколько адресов. Многие
пользователи имеют несколько адресов электронной почты, например, лич-
ный и служебный. Можно предоставить им возможность применять несколько
адресов за счет перемещения сохраненных адресов из таблицы пользователей
в таблицу учетных записей. В таком случае потребуется внести изменения и в
другие фрагменты кода. Форма отправки сообщения должна будет содержать
поле со списком, в котором можно выбирать используемый адрес.
Добавьте возможность отправки, приема и просмотра сообщений с вложе-
ниями. Если пользователи смогут отправлять вложения, потребуется преду-
смотреть функции загрузки файлов, речь о которых шла в главе 18. Вопросы
отправки почты с вложениями подробно рассматриваются в главе 30.
Реализуйте функциональность адресной книги.
Добавьте возможности чтения сетевых новостей. Чтение из NNTP-сервера с
помощью IMAl’-функций практически не отличается от чтения сообщений из
почтового ящика. Достаточно лишь при вызове функции imap_open () указать
другой номер порта и протокол. Вместо указания имени почтового ящика
INBOX можно задать имя группы новостей, из которой и будет выполняться
чтение. Если скомбинировать результат с функциями создания потоков из про-
екта, представленного в главе 31, можно получить полноценное многопоточ-
ное Web-приложение чтения новостей.
Что дальше
В следующей главе мы рассмотрим еще один проект, связанный с электронной
почтой. Это приложение будет выполнять отправку информационных бюллетеней на
многие темы лицам, которые осуществили подписку на вашем сайте.
Глава 29. Разработка почтовой Web-службы
681
30
Разработка диспетчера
списков рассылки
После того как мы встроили базу подписчиков в свой Web-сайт, было бы очень
неплохо иметь возможность поддерживать с ними постоянный контакт, перио-
дически отправляя им информационные бюллетени. В этой главе мы реализуем ин-
терфейс диспетчера списков рассылки. Некоторые диспетчеры списков рассылки
позволяют каждому подписчику отправлять сообщения другим подписчикам. Наша
программа будет представлять собой систему информационных бюллетеней, в кото-
рой отправлять сообщения сможет только администратор. Давайте назовем ее “Пи-
рамида” (“Pyramid-MLM”).
Эта система должна быть похожей на множество других доступных на рынке про-
грамм. Получить некоторое представление о стоящих перед нами задачах можно на
сайте по адресу:
http://www.topics.com
Разрабатываемое нами приложение будет давать администратору возможность
создавать несколько списков рассылки и отправлять информационные бюллетени
отдельно в каждый из этих списков. Приложение будет использовать загрузку фай-
лов, чтобы администратор мог загружать текстовые и HTML-версии информацион-
ных бюллетеней, созданные заранее в автономном режиме. Другими словами, для
создания информационных бюллетеней администраторы могут пользоваться любы-
ми программами по своему выбору.
Пользователи смогут подписываться на любые списки, представленные на нашем
сайте, и выбирать, в какой форме они желают получать информационные бюллете-
ни — простой текст или HTML-формат.
В главе будут рассмотрены следующие темы:
Загрузка множества файлов.
Вложения почтовых сообщений и MIME-кодирование.
Почтовые сообщения в HTML-формате.
Способы управления паролями без вмешательства со стороны пользователей.
Задача
Мы планируем создать интерактивную систему компоновки и отправки инфор-
мационных бюллетеней. Эта система должна позволять создание и рассылку поль-
зователям различных информационных бюллетеней, а также предоставлять поль-
зователям возможность подписываться на один или несколько информационных
бюллетеней.
Если конкретно, система должна удовлетворять следующим требованиям:
Администраторы должны иметь возможность настраивать и изменять списки
рассылки.
Администраторы должны иметь возможность рассылать информационные
бюллетени в текстовом и HTML-формате всем подписчикам в рамках одного
списка рассылки.
Пользователи должны иметь возможность регистрироваться на сайте, а также
вводить и изменять сведения о себе.
Пользователи должны иметь возможность подписываться на любые списки,
доступные на сайте.
Пользователи должны иметь возможность отменять подписку на ранее подпи-
санные списки рассылки.
Пользователи должны иметь возможность сохранять свои предпочтения отно-
сительно получения информационных бюллетеней в формате HTML или в ви-
де простого текста.
В целях поддержания безопасности пользователи не должны иметь возмож-
ность отправлять сообщения электронной почты в списки рассылки и видеть
адреса электронной почты других подписчиков.
Пользователи и администраторы должны иметь возможность просматривать
информацию о списках рассылки.
Пользователи и администраторы должны иметь возможность просматривать
прошлые информационные бюллетени, отправленные в список рассылки (то
есть архив).
Компоненты решения
Для удовлетворения предъявляемых к приложению требований потребуется мно-
жество компонентов. К основным из них относятся: определение базы данных спи-
сков, подписчиков и заархивированных информационных бюллетеней; загрузка ин-
формационных бюллетеней, которые были созданы в автономном режиме: отправка
сообщений электронной почты с вложениями.
Создание базы данных списков и подписчиков
Для каждого пользователя системы в этом проекте мы планируем отслеживать его
имя и пароль, а также перечень списков рассылки, на которые тот или иной пользо-
ватель подписался. Кроме того, мы будем хранить предпочтения каждого пользова-
Глава 30. Разработка диспетчера списков рассылки
683
теля относительно получения сообщений в виде простого текста или же в HTML-
формате, дабы ему можно было отправлять соответствующую версию информацион-
ного бюллетеня.
В качестве администратора будет выступать пользователь, наделенный особыми
правами по созданию новых списков рассылки и отправке в них информационных
бюллетеней.
Для системы такого рода весьма желательно поддерживать архив ранее отправ-
ленных информационных бюллетеней. Подписчики могут не хранить предшествую-
щие сообщения, тем не менее, вполне возможно, что со временем они захотят про-
смотреть некоторые из них. Архив может также служить в качестве маркетингового
инструмента, поскольку потенциальные подписчики будут иметь возможность по-
смотреть, как в общем случае выглядят информационные бюллетени.
Создание этой базы данных в среде MySQL и разработка PHP-интерфейса к ней не
должно представлять собой ничего особо нового или сложного.
Загрузка файлов
Как уже упоминалось ранее, нам необходим интерфейс, который позволил бы ад-
министратору отправлять информационные бюллетени. Тем не менее, мы еще ниче-
го не сказали о том, как администраторы будут создавать эти самые бюллетени. Мож-
но было бы предусмотреть форму, в которой администраторы смогли бы вводить или
вставлять содержимое бюллетеня. Однако, памятуя о принципах дружественного
пользовательского интерфейса, администраторам будет гораздо удобнее, если они
смогут создавать бюллетени в предпочитаемом ими редакторе, а затем загружать ре-
зультирующие файлы на Web-сервер. Помимо прочего, это должно упростить адми-
нистраторам процедуру добавления изображений в информационный бюллетень,
представленный в HTML-формате.
Для решения очерченной задачи можно воспользоваться возможностью загрузки
файлов, которая была описана в главе 18.
Нам придется создать несколько более сложную форму, нежели те, которые при-
менялись в предыдущих проектах. Напомним, что мы решили предоставить админи-
страторам возможность загружать как текстовую, так и HTML-версию бюллетеня, а
также любые изображения, встроенные в HTML-код.
После того как информационный бюллетень успешно загружен, администратор
должен иметь интерфейс, который бы позволил ему просмотреть бюллетень перед
его отправкой. В результате администратор сможет убедиться в корректности загруз-
ки всех файлов.
Следует отметить, что все эти файлы будут также сохраняться и в каталоге архива,
что даст возможность просматривать их в будущем. Доступ по записи в такой каталог
должен быть открыт для пользователя, под именем которого выполняется Web-
сервер. Сценарий загрузки будет предпринимать попытку сохранить бюллетень в ка-
талог архива . /archive/, поэтому обязательно убедитесь в том, что упомянутый ка-
талог создан, а полномочия на доступ к нему установлены корректно.
684
Часть V. Реальные проекты на РНР и MySQL
Отправка сообщений электронной почты
с вложениями
В этом проекте мы хотели бы, чтобы в соответствии с предпочтениями пользова-
телей бюллетени можно было отправлять им в форме простого текста или в виде “ук-
рашенной” HTML-версии.
Для отправки HTML-файла с внедренными в него изображениями потребуется
определиться со способом отправки вложений. Простая PHP-функция mail() не
обеспечивает удобной поддержки отправки вложений. Поэтому вместо нее мы будем
использовать замечательный пакет Mail_Mime из библиотеки PEAR, который был
разработан Ричардом Хейесом (Richard Heyes). Это пакет умеет обрабатывать HTML-
вложения, поэтому он будет использоваться для вложения любых изображений, со-
держащихся в HTML-файле.
Инструкции по установке пакета Mail_Mime можно найти в приложении А. в час-
ти, посвященной инсталляции библиотеки PEAR.
Обзор решения
При написании кода этого проекта мы снова воспользуемся подходом, управляе-
мым событиями, как это уже имело место в главе 27.
Как и ранее, мы начинаем разработку с представления набора диаграмм, изобра-
жающих последовательности действий, которые пользователь может выполнять в
системе. В данном случае мы имеем три диаграммы, представляющие три различных
набора взаимодействий пользователя с системой. Пользователь имеет право выпол-
нять различные действия, когда он не зарегистрирован в системе, когда он зарегист-
рирован в качестве рядового пользователя и когда он зарегистрирован в качестве
администратора. Возможные действия показаны на рисунках 30.1, 30.2 и 30.3, соот-
ветственно.
На рис. 30.1 изображены действия, которые может предпринять незарегистриро-
ванный пользователь. Легко заметить, что он может зарегистрироваться (если уже
имеет учетную запись), создать учетную запись (если он ее еще не имеет) или про-
смотреть списки рассылки, доступные для подписки (это является частью маркетин-
говой тактики).
Рис. 30.1. Незарегистрированный пользова-
тель имеет доступ только к ограниченному
набору действий
Глава 30. Разработка диспетчера списков рассылки
685
Действия, которые пользователь может предпринять после регистрации, показа-
ны на рис. 30.2. Пользователь может изменять параметры настройки своей учетной
записи (адрес электронной почты и предпочтения), изменять свой пароль и изме-
нять набор списков рассылки, на которые он подписался ранее.
Рис. 30.2. После входа в систему пользователь может
изменять свои предпочтения через набор опций
Действия, доступные для зарегистрированного в системе администратора, можно
видеть на рис. 30.3. Как следует из этого рисунка, администратору доступно большин-
ство действий, разрешенных для рядового пользователя, а также набор дополнитель-
ных возможностей. Администратор может создавать новые списки рассылки, созда-
вать новые сообщения для списков рассылки путем загрузки файлов и просматривать
сообщения перед их отправкой.
Рис. 30.3. Администратору доступно множество дополнительных действий
686
Часть V. Реальные проекты на РНР и MySQL
Поскольку приложение использует подход, управляемый событиями, основная
часть приложения содержится в единственном файле index. php, который содержи!
вызовы множества библиотечных функций. Краткое описание файлов этого прило-
жения приведено в табл. 30.1.
Таблица 30.1. Файлы приложения диспетчера списков рассылки
Имя файла Tun Описание
index.php Приложение Главный сценарий, реализующий приложение в целом.
include_fns.php Функции Коллекция включаемых файлов для данного приложения
data_valid_fns.php Функции Коллекция функций для проверки вводимых данных.
db_fns.php Функции Коллекция функций для подключения к базе данных mlm.
mlm_fns.php Функции Коллекция функций, специфичных для данного при- ложения.
output_fns.php Функции Коллекция функций для вывода HTML-содержимого.
upload.php Компонент Сценарий, который управляет компонентом загрузки файлов со стороны администратора. Этот сценарий выделен в отдельный файл для упрощения поддержки должного уровня безопасности.
user_auth_fns.php Функции Коллекция функций для аутентификации пользователей.
create_database.sql SQL SQL-код для создания базы данных mlm и для создания Web-пользователя и пользователя с правами админи- стратора.
Реализацию проекта мы начнем с создания базы данных, в которой будем хранить
информацию о подписчиках и списках рассылки.
Создание базы данных
Для целей этого приложения мы должны хранить следующую информацию:
lists (списки) — списки рассылки, доступные для подписки.
subscribers (подписчики) — пользователи системы и их предпочтения.
sub_lists (списки, на которые совершена подписка) — информация о списках,
на которые подписались пользователи (отношение типа “многие ко многим”).
mail (сообщения электронной почты) — информация об отправленных сооб-
щениях электронной почты.
images (изображения) — поскольку необходимо располагать возможностью от-
правки сообщений электронной почты, состоящих из нескольких файлов (то
есть текста, HTML-кода и набора изображений), требуется также отслеживать,
какие изображения отправляются с каждым почтовым сообщением.
SQL-код, с помощью которого создается эта база данных, показан в листинге 30.1.
Глава 30. Разработка диспетчера списков рассылки
687
Листинг 30.1. create database. sql — SQL-код для создания базы данных mlm
create database mlm;
use mlm;
create table lists
(
listid int auto_increment not null primary key,
listname char(20) not null,
blurb varchar(255)
);
create table subscribers
(
email char(100) not null primary key,
realname char(100) not null,
mimetype char(l) not null,
password char(40) not null,
admin tinyint not null
) ;
# хранит отношение между подписчиком и списком
create table sub_lists
(
email char(100) not null,
listid int not null
) ;
create table mail
(
mailid int auto_increment not null primary key,
email char(100) not null,
subject char(100) not null,
listid int not null,
status cnar(lO) not null,
sent datetime,
modified timestamp
/ I
# хранит изображения, отправляемые в составе конкретного сообщения
create table images
(
mailid int not null,
path char(100) not null,
mimetype char(100) not null
t ,
grant select, insert, update, delete
on mlm.*
to mlmSlocalhost identified by 'password';
insert into subscribers values
('admin@localhost', 'Администратор', 'H', shal('admin'), 1);
688
Часть V. Реальные проекты на РНР и MySQL
Как обычно, этот SQL-код выполняется с помощью следующей командной строки:
mysql -u root -р < create_database.sql
При этом потребуется ввести свой пароль привилегированного пользователя
(root). (Разумеется, этот сценарий можно выполнить, зарегистрировавшись под име-
нем любого пользователя MySQL, обладающего соответствующими правами доступа;
в данном случае права доступа пользователя root были указаны только ради просто-
ты.) Прежде чем выполнять этот код, в нем следует изменить пароль для пользовате-
ля mlm и администратора.
Некоторые поля в этой базе данных требуют небольших дополнительных поясне-
ний, поэтому давайте кратко ознакомимся с их назначением.
Таблица lists содержит поля идентификатора списка рассылки (listid) и его
наименования (listname). Кроме того, в этой таблице содержится поле blurb, в ко-
тором фиксируется описание самого списка рассылки.
Таблица subscribers хранит адреса электронной почты (email) и реальные име-
на подписчиков (realname). В ней хранятся также их пароли password и флаги
(admin), указывающие, является ли данный пользователь администратором. В поле
mime type хранится тип сообщений электронной почты, предпочитаемый данным
пользователем. Он может иметь значение Н для HTML-варианта или Т для текстового
варианта.
Таблица sublists содержит адреса электронной почты (email) из таблицы
subscribers и идентификаторы списков listid из таблицы lists.
Таблица mail содержит информацию о каждом сообщении электронной почты,
отправляемом в рамках системы. В ней хранится уникальный идентификатор
(mailid), адрес электронной почты, откуда отправлено сообщение (email), строка
темы сообщения (subject), а также идентификатор listid списка, в который оно
было или будет отправлено. Фактический текст или HTML-код сообщения может
быть большим файлом, поэтому архив собственно сообщений будет храниться вне
базы данных. Кроме того, мы планируем отслеживать некоторую информацию об
общем состоянии: отправлено ли сообщение (status), когда оно было отправлено
(sent) и метку времени, указывающую время последнего изменения этой записи
(modified).
И, наконец, таблица images используется для отслеживания любых изображений,
связанных с сообщениями в HTML-формате. Эти изображения также могут оказаться
достаточно большими, поэтому с целью повышения эффективности они будут хра-
ниться вне базы данных. Вместо них мы будем отслеживать идентификаторы mailid,
с которыми они связаны, путь path к месту реального хранения изображения и
MIME-тип изображения mimetype, например, image/gif.
В приведенном ранее SQL-коде, помимо таблиц, создается пользователь, от имени
которого будет осуществляться подключение к системе, а также пользователь с пра-
вами администратора этого приложения.
Глава 30. Разработка диспетчера списков рассылки
689
Архитектура сценария
Как и ранее, в этом проекте используется подход, управляемый событиями. Ос-
новная часть приложения хранится в файле index.php. В этом сценарии можно вы-
делись следующие четыре раздела:
1. Предварительная обработка: реализует обработку, которая должна быть вы-
полнена до отправки заголовков.
2. Создание и отправка заголовков: создает и отправляет начало HTML-стра-
ницы.
3. Выполнение действия: отвечает на переданное событие. Как и в предыдущем
примере, событие содержится в переменной $action.
4. Отправка нижних колонтитулов.
Большинство обработки выполняется внутри этого файла. Как упоминалось ра-
нее, приложение использует также библиотеки функций, список которых можно
найти в табл. 30.1.
Полный текст сценария index. php приведен в листинге 30.2.
Листинг 30.2. index.php — главный файл приложения “Пирамида”
<?php
/**********************************************************************
* Раздел 1. Предварительная обработка
*********************************************************************/
require_once ('include_fns.php');
session_start();
$action = $_GET['action;
$buttons = arrayO;
// Добавлять к этой строке, если что-то выполняется перед выводом заголовка
$status = ’';
// Необходимо сначала обработать запросы на вход и выход из системы
if($_POST['email']&&$_POST['password'])
{
$login = login($_POST['email'], $_POST['password']);
if($login == 'admin')
{
$status .= "<pxb>" .get_real_name($_POST[ 'email' ]) .
"</b> вошел в систему"." успешно как <Ь>администратор</Ь></р>
<br /><Ьг /><Ьг /><Ьг /><Ьг />";
$_SESSION['admin_user'] = $_POST['email'];
}
else if($login == 'normal')
{
$status .= "<p><b>".get_real_name($_POST['email'])."</b>
вошел в систему''." успешно.</p><br /xbr />";
$_SESSION['normal_user'] = $_POST['email'];
}
else
{
690
Часть V. Реальные проекты на РНР и MySQL
$status .= "<p> Извините, вход в систему с данным адресом электронной
почты и паролем невозможен.</pxbr />";
}
}
if($action == 'log-out')
{
unset($action),-
unset($_SESSION);
session_destroy();
}
/*************'******х*'*********хх****хж***х**х>г****************'********
* Раздел 2. Формирование и вывод заголовков
*********iii*****i*i*i*****t*iii*i:***i:iit******iti**i********i*****ii у
// Настроить кнопки, которые будут отображаться в панели инструментов
if(check_normal_user())
{
// Для рядового пользователя
$buttons[0| = 'change-password';
$buttons[l] = 'account-settings';
$buttons[2] = 'show-my-lists';
$buttons[3] = 'show-other-lists';
$buttons[4] = 'log-out';
}
else if(check_admin_user())
{
// Для администратора
$buttons[0] = 'change-password';
$buttons[l] = 'create-list';
$buttons[2] = 'create-mail';
$buttons[3] = 'view-mail';
$buttons[4] = 'log-out';
$buttons[5] = 'show-all-lists';
$buttons[6] = 'show-my-lists';
$buttons[7] = 'show-other-lists';
}
else
{
// Если вход в систему еще не совершен
$buttons[0] = 'new-account';
$buttons[l] = 'show-all-lists';
$buttons[4] = 'log-in';
}
if($action)
{
// Вывести заголовок с именем приложения и описанием страницы или действия
do_html_header('Пирамида - '.format_action($action));
}
else
{
// Вывести заголовок только с именем приложения
do_html_header('Пирамида');
Г лава 30. Разработка диспетчера списков рассылки
691
display_toolbar($buttons);
// Вывести любой текст, сгенерированный функциями, которые вызваны до заголовка
echo $status;
/*****★****************************************************************
* Раздел 3. Выполнение действия
*********о********о*******************о***************************у
//До входа в систему доступны только эти действия
switch ( $action )
{
case 'new-account1 :
{
// Избавиться от переменных сеанса
session_destroy();
display_account_form();
break;
}
case 'store-account' :
(
if (store_account($_SESSION[1normal_user1],
$_SESSION[1admin_user'], $_POST))
$action - '';
if(!check_logged_in())
display_login_form($action);
break;
}
case 'show-all-lists' :
{
display_iterns('Все списки', get_all_lists(), ’information',
'show-archive',1');
break;
}
case 'show-archive1 :
{
display_items('Архив для '.get_list_name($_GET['id']),
get_archive($_GET['id']), 'view-html',
'view-text', '');
break;
}
case 'information' :
{
display_information($_GET['id']};
break;
}
default :
(
if(!check_logged_in())
display_login_form($action);
break;
692
Часть V. Реальные проекты на РНР и MySQL
11 Все другие действия требуют входа в систему
if(check_logged_in())
{
switch ( $action )
{
case 'account-settings' :
{
display_account_form(get_email(),
get_real_name(get_email()), get_mimetype(get_email()));
break;
)
case 'show-other-lists' :
{
display_items('Неподписанные списки',
get_unsubscribed_lists(get_email()), 'information',
'show-archive', 'subscribe');
break;
}
case 'subscribe' :
{
subscribe(get_email(), $_GET['id']);
display_items('Подписанные списки', get_subscribed_lists(get_email()),
'information', ’show-archive', 'unsubscribe');
break;
}
case ’unsubscribe' :
{
unsubscribe(get_email(), $_GET['id']);
display_items('Подписанные списки', get_subscribed_lists(get_email()),
'information', 'show-archive', 'unsubscribe');
break;
}
case '’:
case 'show-my-lists' :
{
display_items('Подписанные списки', get_subscribed_lists(get_email()),
'information', 'show-archive', 'unsubscribe');
break;
}
case 'change-password' :
{
display_password_form();
break;
}
case 'store-change-password' :
{
if(change_password(get_email(), $_POST['old_passwd'],
$_POST['new_passwd'], $_POST['new_passwd2']))
{
echo '<p>OK: Пароль изменен.</p>
<br /xbr /xbr /xbr /xbr /xbr />';
}
else
Глава 30. Разработка диспетчера списков рассылки
693
{
echo '<р>Извините, ваш пароль не может быть изменен.</р>';
display—password-form();
}
break;
}
// Следующие действия доступны только администратору
if(check_admin_user())
{
switch ( $action )
{
case 'create-mail' :
{
display—mail_form(get_email()) ;
break;
}
case 'create-list' :
{
display_list_form(get_email());
break;
}
case 'store-list' :
{
if(store_list($_SESSION['admin_user'], $_POST))
{
echo '<р>Новый список добавлен</рхЬг />';
display_items('Все списки', get_all_lists(), 'information',
'show-archive','');
}
else
echo '<р>Невозможно создать список. Пожалуйста, повторите '
.'попытку. </pxbr /xbr /xbr /xbr /xbr />' ;
break;
}
case 'send' :
{
send($_GET['id'] , $_SESSION['admin_user']) ;
break;
}
case 'view-mail' :
{
display_items('Неотправленные сообщения', get_unsent_mail(get_email()),
'preview-html', 'preview-text', 'send');
break;
}
694
Часть V. Реальные проекты на РНР и MySQL
/**********************************************************************
* Раздел 4. Вывод нижнего колонтитула
******i***i**^i**i*iii*'ir******i*f**i*‘ir*i’***ii***i***i*i*x**f****jr**** /
do_html_footer();
9>
В листинге легко заметить ранее упомянутые четыре раздела кода. На этапе пред-
варительной обработки создается сеанс и реализуются действия, которые должны
быть выполнены до отправки заголовков. В данном случае в их число входят регист-
рация и выход из системы.
На этапе обработки заголовков создаются кнопки меню, которые будет ви-
деть пользователь, и с помощью функции do_html_header () из библиотеки
output_fns.php отображаются соответствующие заголовки. Эта функция всего лишь
выводит строку заголовка и меню, поэтому подробно ее рассматривать мы не будем.
В основном разделе сценария мы реализуем ответ на инициированные пользо-
вателем действия. Эти действия разделены на три поднабора: действия, которые
могут выполняться, когда пользователь еще не вошел в систему; действия, которые
могут выполняться рядовыми пользователями; действия, которые могут выпол-
няться пользователями, обладающими правами администратора. Возможность дос-
тупа к последним двум поднаборам действий проверяется с помощью функций
check_logged_in() и check_admin_user (). Эти функции определены в библиотеке
user_fns .php. Их код, а также код функции check_normal_user (), можно найти в
листинге 30.3.
Листинг 30.3. Функции из библиотеки user_auth_fns .php — эти функции проверяют,
зарегистрирован ли пользователь, и если да — то на каком уровне
function check_normal_.user ()
/ ' Определяет, вошел ли пользователь в систему, и уведомляет его, если это не так
{
if (isset($_SESSION[’normal_user1]))
return true;
else
return false;
}
function check_admin_user()
// Определяет, вошел ли пользователь в систему, и уведомляет его, если это не так
if (isset($_SESSION[1admin_user1]))
return true;
else
return false;
)
function check_logged_in()
{
return ( check_normal_user() || check_admin_user() );
}
Глава 30. Разработка диспетчера списков рассылки
695
Как видите, для проверки, вошел ли пользователь в систему, в этих функциях ис-
пользуются переменные сеанса normal_user и admin_user. Установка этих перемен-
ных сеанса рассматривается несколько позже.
В заключительном разделе сценария мы отправляем нижний HTML-колонтитул,
используя для этой цели функцию do_html_f ooter () из библиотеки output_fns .php.
Давайте кратко рассмотрим действия, которые пользователь может иницииро-
вать в системе. Все возможные действия перечислены в табл. 30.2.
Таблица 30.2. Возможные действия в приложении диспетчера списков рассылки
Действие Кто может выполнять Описание
log-in Любой пользователь Отображает пользователю форму входа в систему.
log-out Любой пользователь Завершает сеанс.
new-account Любой пользователь Создает новую учетную запись пользователя.
store-account Любой пользователь Сохраняет подробную информацию по учетной записи.
show-all-lists Любой пользователь Отображает список доступных списков рассылки.
show-archive Любой пользователь Отображает заархивированные информационные бюллетени для конкретного списка рассылки.
information Любой пользователь Отображает основные сведения о конкретном списке рассылки.
account-settings Зарегистрированный пользователь Отображает параметры настройки учетной за- писи пользователя.
show-other-lists Зарегистрированный пользователь Отображает списки рассылки, на которые поль- зователь не подписался.
show-my-lists Зареги стри рованн ый пользователь Отображает списки рассылки, на которые поль- зователь подписался.
subscribe Зареги стри рован ный пользователь Выполняет подписку на конкретный список рассылки.
unsubscribe Зарегистрированный пользователь Отменяет подписку на конкретный список рас- сылки.
change-password Зарегистрированный пользователь Отображает форму для изменения пароля.
store-change- Зарегистрированный Обновляет пароль пользователя в базе данных
password пользователь паролей.
create-mail Администратор Отображает форму для загрузки информацион- ных бюллетеней.
create-list Администратор Отображает форму для создания новых списков рассылки.
store-list Администратор Сохраняет сведения о списке рассылки в базе данных.
view-mail Администратор Отображает информационные бюллетени, ко- торые были загружены, но еще не отправлены.
send Администратор Отправляет информационные бюллетени под- писчикам.
696
Часть V. Реальные проекты на РНР и MySQL
Следует отметить, что действие store-mail, которое фактически загружает
информационные бюллетени, введенные администраторами с помощью действия
create-mail, в этой таблице не присутствует. Это единственное действие, которое в
действительности реализовано в другом файле — upload.php. Мы намеренно помес-
тили его в другой файл, поскольку это упрощает решение проблем, связанных с безо-
пасностью.
Ниже мы обсудим реализацию всех действий в соответствии с тремя группами,
перечисленными в табл. 30.2, то есть: действия, выполняемые пользователями, кото-
рые не вошли в систему; действия, выполняемые пользователями, которые вошли в
систему; действия, выполняемые администраторами.
Реализация процедуры входа в систему
Весьма желательно, чтобы новые пользователи, посещающие сайт, выполнили
три действия. Во-первых, посмотрели на то, что им предлагается; во-вторых, зареги-
стрировались на сайте; в-третьих, вошли в систему. Давайте по очереди рассмотрим
эти три действия.
На рис. 30.4 показано окно, отображаемое при первом посещении сайта.
Создание учетной записи и вход в систему будут рассмотрены в этом разделе, а к
просмотру сведений о списке рассылке мы вернемся в разделах “Реализация функций
пользователя” и “Реализация функций администратора”.
Рис. 30.4. При первом посещении пользователи могут создать но-
вую учетную запись, просмотреть доступные списки рассылки пли
просто войти в систему
Глава 30. Разработка диспетчера списков рассылки
697
Создание новой учетной записи
Когда пользователь выбирает пункт меню New Account (Новая учетная запись),
инициируется действие new-account. В результате активизируется следующий фраг-
мент кода из сценария index. php:
case 'new-account' :
{
// Избавиться от переменных сеанса
session_destroy();
display_account_form();
break;
}
По сути дела, этот фрагмент кода осуществляет выход пользователя из системы,
если он ранее в нее вошел, и отображает форму сведений по учетной записи, пока-
занную на рис. 30.5.
Рис. 30.5. Форма создания новой учетной записи дает пользовате-
лям возможность ввести необходимые детали
Эта форма генерируется функцией display_account_f orm () из библиотеки
output_fns .php. Упомянутая функция используется как в данном действии, так и в
действии по настройке параметров учетной записи для вывода формы, в которой
пользователь может настраивать учетную запись. Если функция вызывается при об-
работке действия по настройке учетной записи, форма будет заполняться сущест-
вующими данными учетной записи пользователя. В данном случае форма пуста и го-
това для ввода детальной информации о новой учетной записи. Поскольку эта
функция выводит только HTML-код, она не рассматривается подробно.
Кнопка отправки этой формы инициирует действие store-account, которое об-
рабатывается следующим фрагментом кода:
698
Часть V. Реальные проекты на РНР и MySQL
case
1 store-account'
{
if (store_account($_SESSION['normal_user'] ,
$—SESSION['admin_user'], $_POST))
Section = ' 1 ;
if(!check_logged_in())
display_login_form($action);
break;
}
Функция store_account() сохраняет сведения об учетной записи в базе данных.
Код этой функции можно найти в листинге 30.4.
Листинг 30.4. Функция store_account () из библиотеки mlm_ fns.php — эта функция
добавляет нового пользователя в базу данных или изменяет информацию о
существующем пользователе
function store_account($normal_user, $admin_user, Sdetails)
/I Добавляет в базу данных нового подписчика либо дает возможность
// пользователю изменить ранее введенную информацию
{
if(!filled_out(Sdetails))
{
echo 'Необходимо заполнить все поля. Повторите попытку.<br /><Ьг />';
return false;
}
else
{
if(subscriber_exists(Sdetails['email']))
{
// Проверить, изменяется ли информация именно для того пользователя,
// который вошел в систему
if (get_email () ==$details [ 1 email' ] )
{
Squery = "update subscribers set realname = 1Sdetails[realname]',
mimetype = 'Sdetails[mimetype]'
where email = '" . Sdetails[email] . ................;
if($conn=db_connect())
{
i f($conn->query(Squery))
return true;
else
return false;
}
else
{
echo 'Невозможно сохранить изменения.<br /xbr /xbr /xbr /xbr /xbr />';
return false;
}
)
else
{
echo '<р>Извините, этот адрес электронной почты уже зарегистрирован.</р>';
echo '<р>Для изменения настроек вы должны войти в систему '
Глава 30. Разработка диспетчера списков рассылки
699
с использованием этого адреса.</р>';
return false;
}
}
else // Новая учетная запись
{
$query = "insert into subscribers
values ('$details[email]',
'$details[realname]',
'$details[mimetype]',
shal('$details[new_password]'),
0) " ;
if($conn=db_connect())
{
if($conn->query($query))
return true;
else
return false;
}
else
{
echo 'Невозможно сохранить информацию о новой учетной записи.<br />
<br /xbr /xbr /xbr /xbr />' ;
return false;
}
}
}
}
Вначале эта функция проверяет, заполнил ли пользователь все необходимые поля
формы. Если все в порядке, функция либо создает нового пользователя, либо обнов-
ляет сведения об учетной записи для существующего пользователя. Пользователь мо-
жет обновить сведения об учетной записи только после входа в систему под соответ-
ствующим именем.
Авторизация вошедшего пользователя осуществляется с помощью функции
get_email(), которая получает адрес электронной почты текущего пользователя,
вошедшего в систему. Мы вернемся к этой функции немного позже, поскольку в ней
задействованы переменные сеанса, устанавливаемые во время входа в систему.
Вход в систему
После того как пользователь заполнил форму входа в систему', показанную на рис.
30.4, и щелкнул на кнопке Log In (Войти), запускается сценарий index.php с установ-
ленными значениями переменных email и password. В результате активизируется
код входа в систему, относящийся к этапу предварительной обработки сценария. Со-
ответствующий фрагмент кода показан ниже:
// Необходимо сначала обработать запросы на вход и выход из системы
if($_POST['email']&&$_POST['password'])
{'
$login = login($POST['email'], $_POST['password']);
700
Часть V. Реальные проекты на РНР и MySQL
if(Slogin == 'admin')
{
Sstatus .= "<pxb>".get_real_name($_POST['email']).
"</Ь> вошел в систему”.1' успешно как <Ь>администратор</Ьх/р>
<br /xbr /xbr /><Ьг /><Ьг />";
$_SESSION['admin_user'] = $_POST['email'];
}
else if($login = - 'normal')
{
Sstatus .= "<pxb>" .get_real_name($_POST[ 'email' ] )
.”</b> вошел в систему"
. " успешно.</рхЬг /xbr />";
$_SESSION['normal_user'] = $_POST['email'];
}
else
{
Sstatus .= "<p> Извините, вход в систему с данным адресом электронной
почты и паролем невозможен. </pxbr />";
}
}
if($action == 'log-out')
{
unset(Section);
unset($_SESSION);
session_destroy();
}
Как видите, сначала предпринимается попытка войти в систему с помощью функ-
ции login() из библиотеки user_auth_fns.php. Эта функция несколько отличается
от функций регистрации, использованных в других примерах, поэтому давайте ее
рассмотрим более подробно. Код функции login () представлен в листинге 30.5.
Листинг 30.5. Функция login () из библиотеки userauthfns .php — эта функция
проверяет регистрационные сведения пользователя
function login($email, Spassword)
/I Проверяет с помощью доступа к базе данных переданные имя пользователя
// и пароль. Если совпадение найдено, возвращает тип регистрации,
// в противном случае - значение false
{
// Подключиться к базе данных
Sconn = db_connect();
if (!Sconn)
return 0;
Squery = "select admin from subscribers
where email='Semail'
and password = shal ('Spassword')'';
Sresult = Sconn->query(Squery);
if ((Sresult)
return false;
if ($result->num_rows<l)
return false;
Глава 30. Разработка диспетчера списков рассылки
701
$row = $result->fetch_array();
if($row[0] == 1)
return 'admin';
else
return 'normal';
Функции регистрации, которые мы применяли ранее, возвращали значение true в
случае успешной регистрации и false в случае неудачи. В данном случае функция
также возвращает значение false в случае неудачи, однако в случае успешной реги-
страции она возвращает тип пользователя: ' admin' или ' normal'. Тип пользователя
выбирается из столбца admin таблицы подписчиков в соответствие с конкретной
комбинацией адреса электронной почты и пароля. Если не возвращается никакого
результата, функция возвращает значение false. Если пользователь является адми-
нистратором, в столбце должно быть записано значение 1 (true) и функция вернет
строку ' admin'. В противном случае она вернет значение ' normal'.
Возвращаясь к основной процедуре выполнения, мы регистрируем переменную
сеанса для отслеживания того, кем является данный пользователь. Этой перемен-
ной будет либо admin_user, если пользователь является администратором, либо
normal_user, если это обычный пользователь. Какая бы из этих переменных ни была
определена, она будет содержать адрес электронной почты пользователя. С целью
упрощения проверки адреса электронной почты пользователя применяется ранее
упоминавшаяся функция get_email (). Ее код можно найти в листинге 30.6.
Листинг 30.6. Функция get_esiail () из библиотеки user auth_fns .php — эта функция
возвращает адрес электронной почты зарегистрированного пользователя
function get_email()
{
if (isset($_SESSION['normal_user']))
return $_SESSION['normal_user'];
if (isset($_SESSION['admin_user']))
return $_SESSION['admin_user'];
return false;
После возврата в основную программу’ система сообщает пользователю, был ли он
зарегистрирован, и если да, то на каком уровне.
Результат выполнения попытки регистрации показан на рис. 30.6.
Теперь, после входа в систему’ в качестве рядового пользователя, мы должны пе-
рейти к реализации функций пользователя.
Реализация функций пользователя
После входа в систему’ пользователю должны быть доступными пять действий:
Просмотр списков рассылки, доступные для подписки.
Подписка на списки рассылки и отмена подписки на них.
702
Часть V. Реальные проекты на РНР и MySQL
Изменение настройки своих учетных записей.
Изменение своего пароля.
Выход из системы.
Большинство из этих опций можно найти на рис. 30.6. Ниже мы рассмотрим реа-
лизацию каждой из них.
Рис. 30.6. Система сообщает пользователю об успешном входе
Просмотр списков рассылки
В этом проекте мы реализуем набор опций для просмотра доступных списков
рассылки и детальной информации о них. На рис. 30.6 показаны две таких опции:
Show Му Lists (Показать мои списки рассылки), обеспечивающая вывод списков,
на которые подписался данный пользователь, и Show Other Lists (Показать другие
списки рассылки), обеспечивающая вывод списков, на которые данный пользова-
тель пока не подписался.
Если вы снова посмотрите на рис. 30.4, то наверняка заметите, что существует еще
одна опция — Show All Lists (Показать все списки рассылки), которая приводит к ото-
бражению всех доступных в системе списков рассылки. Чт обы система оказалась дей-
ствительно полезной, ее следует дополнить функцией разбиения на страницы (для
отображения, скажем, по 10 списков на каждой странице). Для краткости мы решили
это не делать.
Три перечисленных опции активизируют, соответственно, действия show-my-lists,
show- other -1 ists и show-all-lists. Несложно догадаться, что все эти действия
работают весьма схожим образом. Вот как выглядит код реализации упомянутых
действий:
Глава 30. Разработка диспетчера списков рассылки
703
case 'show-all-lists' :
{
display_items('Все списки', get_all_lists(), 'information',
'show-archive
break;
}
case 'show-other-lists' ;
{
display_iterns('Неподписанные списки',
get_unsubscribed_lists(get_email()), 'information',
'show-archive', 'subscribe');
break;
}
case 'show-my-lists' :
{
display_items('Подписанные списки',
get_subscribed_lists(get_email()),
'information', 'show-archive', 'unsubscribe');
break;
}
Как видите, все эти действия вызывают функцию display_items () из библиотеки
output_fns .php, однако в каждом случае эта функция вызывается с другими парамет-
рами. Кроме того, действия используют также упоминавшуюся ранее функцию
get_email () для получения соответствующего адреса электронной почты для данно-
го пользователя. Результат выбора опции Show Other Lists (действие show-other-
lists) можно видеть на рис. 30.7. На этом рисунке показана страница неподписан-
ных списков.
Давайте рассмотрим код функции display_iterns () более подробно. Этот код
представлен в листинге 30.7.
Рис. 30.7. Функция display_items () используется для вывода переч-
ня списков рассылки, на которые пользователь еще не подписался
704
Часть V. Реальные проекты на РНР и MySQL
Листинг 30.7. Функция display_items () из библиотеки output_fns .php — эта функция
используется для отображения списка элементов и связанных с ними действий
function display_iterns($title, $list, $actionl='', $action2='', $action3='')
{
global $table_width;
echo "<table width='$table_width’ cellspacing='0' cellpadding='0'
border='0'>";
// Подсчитать количество действий
Sections = ((Sactionl!='') + ($action2!=1') + (Saction3!=''));
echo '<tr>
<th colspan=\''. (l+$actions) bgcolor=’#5B69A6'> $title </th>
</tr>";
/I Подсчитать количество элементов
Sitems = sizeof($list);
if(Sitems == 0)
echo '<tr>
<td colspan = " ’ . (l+$actions).'" align=”center”>
Нет элементов для отображения*:/td>
</tr>';
else
{
// Вывести каждую строку
for($i = 0; Sitems; $i++)
{
if($i%2) // Переключение цвета фона
Sbgcolor = "'#ffffff'";
else
$bgcolor = " '#ccccff'';
echo "<tr>
<td bgcolor=$bgcolor
width= '“.($table_width - ($actions*149)) .'\’>';
echo $list[$i][ 1 ] ;
if($list[$i][2])
echo ' - ' ,$list[$i][2];
echo 1</td>';
// Создать кнопки для представления максимум трех действий в строке
for($j = 1; $j<=3; $j++)
{
$var = 'action'.$j;
if($$var)
(
echo "<td bgcolor=Sbgcolor width='149'>”;
// Кнопки view/preview (показать/предварительный просмотр)
// представляют собой специальный случай,
// поскольку они указывают на некоторый файл
if($$var == 'preview-html'||$$var == 'view-html'||
$$var == 'preview-text'| j$$var == 'view-text')
display_preview_button($list[$i][3] , $1ist [$i][0], $$var);
else
display_button( $$var, '&id=' . $list[$i][0] );
Глава 30. Разработка диспетчера списков рассылки
705
echo 1</td>';
}
}
echo "</tr>\n";
}
echo '</table>';
}
2
Эта функция будет выводить таблицу элементов, каждый из которых имеет до
трех связанных с ним кнопок, инициирующих действия. Функция принимает сле-
дующие пять параметров, по порядку:
$title — заголовок, который отображается в верхней части таблицы. В кон-
ретном случае, показанном на рис. 30.7, в качестве заголовка передается “Не-
подписанные списки”; мы обсуждали это ранее, когда рассматривали фрагмент
кода для действия show-other-lists.
$list — массив элементов, которые должны отображаться в каждой строке
таблицы. В данном случае это массив списков рассылки, на которые пользова-
тель не подписан в текущий момент. Мы создаем этот массив < в данном случае)
в функции get_unsubscribed_lists (). которая будет рассмотрена несколько
позже. Это многомерный массив, в котором каждая строка содержит до четы-
рех элементов данных о каждой из строк. Рассмотрим эти элементы:
• Элемент $list[n][0] должен содержать идентификатор элемента, кото-
рым обычно будет номер строки. Этот элемент присваивает кнопкам дейст-
вий идентификатор строки, применительно к которой они должны дейст-
вовать. В данном случае будут использоваться идентификаторы из базы
данных — об этом будет сказано чуть позже.
• Элемент $list[n] [1] должен содержать имя элемента. Им будет текст, ото-
бражаемый для конкретного элемента. Например, в случае, показанном на
рис. 30.7, именем элемента в первой строке таблицы является “РНР Tipsheet”.
• Элементы $list[n][2] и $list[n][3] необязательны. Они используются
для того, чтобы указать на присутствие дополнительной информации, и со-
ответствуют дополнительному информационному тексту и идентификатору
дополнительной информации. Пример использования этих двух парамет-
ров будет приведен при рассмотрении действия View Mail (Просмотреть
почту) в разделе “Реализация функций администратора” этой главы.
Третий, четвертый и пятый параметры функции display_items • использу-
ются для передачи трех действий, которые будут отображаться на кнопках, со-
ответствующих каждому элементу. На рис. 30.7 таковыми являются три функ-
циональных кнопки: Information (Информация), Show Archive (Показать архив!
и Subscribe (Подписаться).
Эти три кнопки для страницы просмотра всех списков были получены за счет пе-
редачи имен действий — information, show-archive и subscribe. За счет использо-
вания функции display_button () упомянутые действия были преобразованы в кноп-
706
Часть V. Реальные проекты на РНР и MySQL
ки с отображаемыми на них словами и связанными с ними соответствующими дейст-
виями.
Как видно из действий, каждое действие, связанное с показом, приводит к раз-
личному способу вызова функции dispay_items (). Помимо того, что каждое из дей-
ствий имеет свой заголовок и свои кнопки, оно же использует собственную функцию
для построения массива отображаемых элементов.
Действие show-all-lists использует функцию get_all_lists(), show-other-
lists — функцию get_unsubscribed_lists(), a show-my-lists — функцию
get_subscribed_lists (). Все эти функции работают аналогичным образом. Они
входят в состав библиотеки функций mlm_fns .php.
Мы решили ознакомиться с кодом функции get_unsubscribed_lists (), поскольку
именно этот пример рассматривался до сих пор. Ее код представлен в листинге 30.8.
Листинг 30.8. Функция get unsubscribed liets () из библиотеки.php —
эта функция служит для создания массива списков рассылки, на которые
пользователь не подписался
function get_unsubscribed_lists(Semail)
// извлекает списки рассылки, на которые данный пользователь ‘не* подписался
{
Slist = arrayO;
Squery = "select lists.listid, listname, email from lists left join sub_lists
on lists.listid = sub_lists.listid
and email='Semail' where email is NULL order by listname";
if($conn=db_connect())
{
Sresult = $conn->query (Squery) ,•
if(1Sresult)
{
echo '<р>Невозможно прочитать список из базы данных.</р>';
return false;
}
$num = $result->num_rows;
for($i = 0; $i<Snum; $i++)
{
$row = $result->fetch_array();
array__push( Slist, array($row[0] , $row[l])l;
}
}
return Slist;
}
Как видите, в эту’ функцию необходимо передать адрес электронной почты. Им
должен быть адрес электронной почты подписчика, с которым выполняется работа.
Функции get_subscribed_lists () также ожидает передачи адреса электронной поч-
ты в качестве параметра, а вот, что должно быть совершенно очевидно, для функции
get_all_lists () подобное не требуется.
Имея адрес электронной почты подписчика, мы подключаемся к базе данных и
получаем все списки рассылки, на которые подписчик не подписался. Для отыскания
несовпадающих элементов применяется конструкция LEFT JOIN.
Главе 30. Разработка диспетчера списков рассыпки
707
Циклический просмотр результатов и построчное построение массива выполня-
ется с использованием встроенной PHP-функции array_push ().
Теперь, когда известно, как создавать этот список, давайте рассмотрим связанные
с этим выводом кнопки действий.
Просмотр сведений о списке рассылки
Кнопка Information (Информация), которую можно видеть на рис. 30.7, запускает
действие information, которое реализовано с помощью следующего кода:
case 'information' :
{
display_information($_GET['id']);
break;
}
Результат выполнения функции display_information () показан на рис. 30.8.
Рис. 30.8. Функция display_inf ormation () отображает детальную
информацию о списке рассылки
Функция отображает некоторые базовые сведения о конкретном списке рассылки,
а также количество подписчиков и количество отправленных в список и доступных в
архиве информационных бюллетеней (подробнее об этом — немного позже).
Полный код этой функции можно найти в листинге 30.9.
Листинг 30.9. Функция display_information () из библиотеки output_fns. php —
эта функция отображает детальную информацию о списке рассылки
function display_information($listid)
{
if(!$listid)
return false;
$info = load_list_info($listid);
708
Часть V. Реальные проекты на РНР и MySQL
if($info)
{
echo ’<h2>'.pretty(Sinfо[listname]).'</h2>’ ;
echo '<p>'.pretty($infо[blurb]);
echo ' </рхр>Количество подписчиков:' . $info[subscribers];
echo ' </рхр>Количество сообщений в архиве:' . $info[archive].'</p>';
)
Для выполнения своей связанной с Web-средой задачи функция display_infor-
mationt) использует две другие функции: load_list_info() и prettyO. Функция
load_list_inf о () действительно получает данные из базы данных. Функция
pretty () просто форматирует эти данные, удаляя из них символы косой черты, пре-
образуя символы новой строки в HTML-дескрипторы <br> и выполняя подобные
действия.
Давайте кратко рассмотрим функцию load_list_info (). Она входит в состав биб-
лиотеки mlm_f ns . php, а ее код можно найти в листинге 30.10.
Листинг 30.10. Функция load_list_infо () из библиотеки mlmfns.php — эта функция
создает массив детальной информации о списке рассылки
function load_list_info($listid)
{
if(!$listid)
return false;
if(!$conn=db_connect() )
return false;
Squery = "select listname, blurb from lists where listid = $listid";
Sresult = $conn->query(Squery) ;
if (!Sresult)
{
echo 'He удается извлечь список';
return false;
}
$infо = $result->fetch_assoc();
Squery = ''select count (*) from sub_lists where listid = $listid“;
Sresult = $conn->query(Squery);
if(Sresult)
{
$row = $result->fetch_array();
$infо['subscribers'] = $row[0];
}
Squery = "select count!*) from mail where listid = Slistid
and status = 'SENT'";
Sresult = $conn->query($query);
if(Sresult)
{
$row = $result->fetch_array();
$infо['archive'] = $row[0];
)
return Sinfo;
Глава 30. Разработка диспетчера списков рассылки
709
Функция выполняет три запроса к базе данных с целью формирования имени и
информационной строки для списка рассылки из таблицы lists, количества подпис-
чиков из таблицы sub_lists и количества отправленных информационных бюллете-
ней из таблицы mail.
Просмотр архивов списков рассылки
Помимо просмотра информационной строки списка рассылки, пользователи мо-
гут просматривать все сообщения электронной почты, которые были отправлены в
список рассылки, выполнив для этого щелчок на кнопке Show Archive (Показать ар-
хив). В результате активизируется действие show-archive, которое приводит к вы-
полнению следующего кода:
case 'show-archive' :
{
display_iterns('Архив для '.get_list_name($_GET['id']),
get_archive($_GET['id']), 'view-html', 'view-text', '');
break;
}
Эта функция также использует функцию display_items () для вывода списка раз-
личных элементов сообщений электронной почты, отправленных в список рассылки.
Элементы извлекаются с помощью функции get_archive () из состава библиотеки
mlm_fns . php. Ее код приведен в листинге 30.11.
Листинг 30.11. Функция get_archive () из библиотеки mlm_f пв. php — эта функция создает
массив архивированных информационных бюллетеней для данного списка рассылки
function get_archive($listid)
{
//Возвращает массив архивированных сообщений электронной почты для данного списка
//Массив содержит строки формата (идентификатор сообщения, тема сообщения)
$list = arrayO;
$listnaine = get_list_name(Slistid);
Squery = "select mailid, subject, listid from mail
where listid = Slistid and status = 'SENT' order by sent";
if($conn=db_connect())
{
$result = $conn->query(Squery) ;
if(!Sresult)
(
echo "<p>He удается извлечь список из базы данных</р>";
return false;
)
$num = $result->num_rows;
for($i = 0; $i<$num; $i++)
{
$row = $result->fetch_array();
$arr_row = array($row[0], $row[l], Slistname, Slistid);
array_push(Slist, $arr_row);
)
}
return Slist;
)
710
Часть V. Реальные проекты на РНР и MySQL
Как и ранее, эта функция извлекает из базы данных требуемую информацию — в
данном случае сведения о сообщениях электронной почты, которые были отправле-
ны, — и создает массив, подходящий для передачи в функцию display_items ().
Подписка и отмена подписки
В перечне списков рассылки, показанном на рис. 30.7, каждый список рассылки
имеет кнопку, которая дает возможность пользователям подписаться на него. Анало-
гично, если пользователи выбирают опцию Show Му Lists (Показать мои списки рас-
сылки) для просмотра списков, на которые они уже подписались, рядом с каждым
списком отображается кнопка Unsubscribe (Отменить подписку).
Эти кнопки активизируют действия subscribe (подписаться) и unsubscribe (от-
менить подписку), которые приводят к выполнению, соответственно, следующих
двух фрагментов кода:
case 'subscribe' :
(
subscribe(get_email(), $_GET['id']);
display_iterns('Подписанные списки',
get_subscribed_lists(get_email() ) ,
'information', 'show-archive’, 'unsubscribe');
break;
}
case 'unsubscribe' :
{
unsubscribe(get_email(), $_GET['id']);
display_items('Подписанные списки',
get_subscribed_lists(get_ emai1()),
'information', 'show-archive', 'unsubscribe');
break;
}
В любом случае вызывается какая-то функция (subscribe () или unsubscribe ()), а
затем с помощью функции display_items () снова выводится перечень списков рас-
сылки, на которые пользователь подписан в текущий момент.
Код функций subscribe () и unsubscribe () можно найти в листинге 30.12.
Листинг 30.12. Функции subscribe () и unsubscribe() из библиотекиmlm_fns.php — эти
функции добавляют и удаляют подписанные списки рассылки для данного пользователя
function subscribe(Semail, Slistid)
{
if(!Semail||!$listid||!list_exists(Slistid)||!subscriber_exists(Semail))
return false;
// Выйти, если подписка на этот список уже совершена
if(subscribed(Semail, Slistid))
return false;
if(!$conn=db_connect())
return false;
Squery = "insert into sub_lists values ('Semail', Slistid)";
Sresult = $conn->query(Squery);
Глава 30. Разработка диспетчера списков рассылки
711
return $result;
}
function unsubscribe($email, $listid)
{
if(1$email||!$listid)
return false;
if(1$conn=db_connect())
return false;
$query = "delete from sub_lists where email = '$email' and listid = $listid";
$result = $conn->guery($query);
return $result;
2
Функция subscribed добавляет в таблицу sub_lists строку, соответствующую
подписке; функция unsubscribe () удаляет эту строку.
Изменение параметров настройки учетной записи
Щелчок на кнопке Account Settings (Параметры настройки учетной записи) акти-
визирует действие account-settings. С этим действием связан следующий код:
case 'account-settings' :
{
display_account_form(get_email(),
get_real_name(get_email()),
get_mimetype(get_email()));
break;
}
Как видите, снова вызывается функция display_account_form(), которая приме-
нялась для первоначального создания учетной записи. Однако на этот раз ей переда-
ются текущие сведения о пользователе, которые с целью упрощения внесения изме-
нений будут отображаться в форме. Как уже отмечалось ранее, когда пользователь
щелкает на кнопке отправки этой формы, активизируется действие store-account.
Изменение пароля
Щелчок на кнопке Change Password (Изменить пароль) активизирует действие
change-password, которое приводит к выполнению следующего кода:
case 'change-password' :
{
display_password_form();
break;
}
Функция display_password_form() из библиотеки output_fns.php просто ото-
бражает форму, в которой пользователь может изменить свой пароль. Эта форму
можно видеть на рис. 30.9.
712
Часть V. Реальные проекты на РНР и MySQL
Рис. 30.9. Функция display_password_form () предоставляет поль-
зователям возможность изменять свои пароли
Когда пользователь щелкает на кнопке Change Password в нижней части этой
формы, активизируется действие store-change-password. С этим действием связан
следующий код:
case 'store-change-password' :
{
if(change_password(get_eraail(), $_POST['old_passwd'j,
$_POST['new_passwd1], $_POST[’new_passwd21]))
{
echo '<p>OK: Пароль изменен.</p>
<br /xbr /xbr /xbr /xbr /xbr />';
}
else
{
echo '<р>Извините, ваш пароль не может быть изменен.</р>';
display_password_form();
}
break;
}
Этот код предпринимает попытку с помощью функции change_password() из-
менить пароль и сообщает пользователю об успехе или неудаче выполнения. Функция
change_password() входит в состав библиотеки user_auth_fns .php, а ее код приве-
ден в листинге 30.13.
Листинг 30.13. Функция change„password() из библиотеки userauthfns .php —
эта функция проверяет и обновляет пароль пользователя
function change„password($email, $old_password, Snew_password,
$new_password_conf)
Глава 30. Разработка диспетчера списков рассылки
713
// Изменяет старый пароль $old_password для адреса Semail на новый пароль
// $new_password. Возвращает значение true или false.
{
// Если старый пароль корректен, изменить пароль на $new_password
//и вернуть значение true. В противном случае вернуть значение false,
if (login(Semail, $old_password))
{
if($new_password==$new_password_conf)
{
if (!($conn = db_connect()))
return false;
Squery = “update subscribers
set password = shal('$new_password')
where email = 'Semail'";
$result = $conn->query(Squery);
return $result;
}
else
echo ’<р>Переданные вами пароли не совпадают.</р>';
}
else
echo '<р>Старый пароль указан неправильно.</р>';
return false; // старый пароль указан неправильно
Эта функция подобна остальным рассмотренным функциям определения и изме-
нения паролей. Она сравнивает пару вновь введенных пользователем паролей, чтобы
убедиться в том, что они совпадают, и если это так, пытается обновить пароль поль-
зователя в базе данных.
Выход из системы
Когда пользователь щелкает на кнопке Log Out (Выход), инициируется действие
log-out. Выполняемый этим действием код находится в разделе предварительной
обработки сценария и имеет следующий вид:
if($action == 'log-out')
{
unset($action);
unset($_SESSION);
session_destroy();
}
Этот фрагмент кода освобождает память, выделенную под переменные сеанса, и
удаляет сеанс. Обратите внимание, что он отменяет также переменную action. Это
значит, что мы будем входить внутрь оператора case основной ветви без какого-либо
действия, то есть будет выполняться следующий код:
case '1:
{
if(!check_logged_in())
display_login_form($action);
break;
}
714
Часть V. Реальные проекты на РНР и MySQL
Это позволит зарегистрироваться другому пользователю или этому же пользова-
телю, но под другим именем.
Реализация функций администратора
Если какой-то пользователь регистрируется как администратор, ему доступны до-
полнительные опции, показанные на рис. 30.10.
Рис. 30.10. Меню администратора разрешает создавать и управ-
лять списками рассылки
Дополнительные опции включают: Create List (Создать список рассылки), который
обеспечивает создание нового списка рассылки; Create Mail (Создать новое сообще-
ние), реализующий функцию подготовки нового информационного бюллетеня; View
Mail (Просмотреть сообщение), с помощью которого выполняется просмотр и отправ-
ка в список рассылки ранее созданных, но не отправленных информационных бюлле-
теней. Давайте по очереди рассмотрим все опции.
Создание нового списка рассылки
Если администратор решает создать новый список рассылки и выполняет щелчок
на кнопке Create List, активизируется действие create-list, с которым связан сле-
дующий код:
case 'create-list' :
{
display_list_form(get_email());
break;
J
Глава 30. Разработка диспетчера списков рассылки
715
Функция display_list_form() отображает форму, которая позволяет админист-
ратору ввести параметры нового списка рассылки. Эта функция входит в состав биб-
лиотеки output_fns.php. Все что она делает, так это всего лишь выводит HTML-
текст, поэтому подробно на ней останавливаться не имеет смысла. Результат ее выво-
да можно посмотреть на рис. 30.11.
Пирамида - Create List - Мозйа lb: д&'уц
Файл Правка Вид Лдэеход Закладки иктрументы Окно Справка Debug QA.
-Ш ГйПьор'ЛЙос^^ Поиск | d,1 fl
i назад Обновить 1 ' ’ ——------’ 11ХЛ
Show Other Lists
A»i Lists
Сэ«аяии«- OKU
Название списка: |Новости Пирамиды
Описание списка:
|ноеюст»Гкампании. Содержит информацию о сотрудниках, инвесторах и другие
1интересные аспекта]
Рис. 30.11. Опция Create List требует, чтобы администратор ввел имя
и описание (или информационную строку) нового списка рассылки
Когда администратор щелкает на кнопке Save List (Сохранить список рассылки),
активизируется действие store-list, которое приводит к выполнению следующего
фрагмента кода из файла index.php:
case 'store-list' :
(
if (store_list($_SESS!ON['admin_user'], $_POST))
{
echo '<р>Новый список добавлен</рхЬг />';
di splay__i terns (' Все списки', get_all_lists(), 'information',
'show-archive
)
else
echo '<р>Невозможно создать список. Пожалуйста, повторите '
.'попытку.</pxbr /xbr /xbr /xbr /><Ьг />';
break;
)
Несложно заметить, что код предпринимает попытку сохранить параметры ново-
го списка рассылки, а затем вывести новый перечень списков рассылки. Параметры
списка рассылки сохраняются с помощью функции store_list (). Код этой функции
показан в листинге 30.14.
716
Часть V. Реальные проекты на РНР и MySQL
Листинг 30.14. Функция store_list () из библиотеки mlmfns .php — эта функция
помещает в базу данных новый список рассылки
function store_list($admin_user, Sdetails)
{
if(!filled_out(Sdetails))
{
echo 'Должны быть заполнены все поля. Повторите попытку.<br /><Ьг />';
return false;
}
else
{
if(!check_admin_user($admin_user))
return false;
// Как эта функция может вызываться кем-то, кто вошел в систему
// не как администратор?
if(!$conn=db_connect())
{
return false;
}
Squery = "select count)*)
from lists
where listname = '{Sdetails['name'])'•;
Sresult = Sconn->query(Squery);
$row = $result->fetch_array();
if($row[0] > 0)
{
echo 'Извините, список с таким именем уже существует.';
return false;
}
Squery = "insert into lists values (NULL,
'{Sdetails['name']}',
'{Sdetails['blurb']}')";
Sresult = $conn->query(Squery);
return Sresult;
}
2
Прежде чем выполнить собственно запись в баз}' данных, эта функция предприни-
мает несколько проверок: она проверяет, введены ли все необходимые данные, являет-
ся ли текущий пользователь администратором, а также уникально ли имя списка рас-
сылки. Если все в порядке, список добавляется в таблицу lists базы данных mlm.
Загрузка нового информационного бюллетеня
Наконец-то мы благополучно добрались до основной задачи этого приложения:
загрузки и отправки информационных бюллетеней в списки рассылки.
Когда администратор щелкает на кнопке Create Mail (Создать новое сообщение),
активизируется действие create-mail, с которым связан следующий фрагмент кода:
Глава 30. Разработка диспетчера списков рассылки
717
case 'create-mail' :
{
display_mail_form(get_email());
break;
}
Для администратора выводится форма, показанная на рис. 30.12.
Вспомните, что при разработке этого приложения предполагалось, что админи-
стратор создал информационный бюллетень в автономном режиме в двух форматах,
HTML и текстовом, и перед отправкой загрузит обе версии. Этот подход был выбран
ради того, чтобы для подготовки информационных бюллетеней администраторы
могли пользоваться своими любимыми программами. В результате удобство работы с
приложением возрастает.
Рис. 30.12. Пункт меню Create Mail предоставляет администратору
интерфейс для загрузки файлов информационных бюллетеней
Как видите, эта форма содержит набор полей, которые должны заполняться ад-
министратором. В верхней части формы располагается выпадающий список списков
рассылки, из которого можно произвести выбор. Администратор должен также за-
полнить поле темы информационного бюллетеня — это строка Тема будущего сооб-
щения электронной почты.
Все остальные поля формы являются полями для загрузки файлов, о чем свиде-
тельствуют расположенные рядом с ними кнопки Обзор. Для того чтобы отправить
информационный бюллетень, администратор должен указать как текстовую, так и
HTML-версию этого бюллетеня (хотя, конечно, при необходимости это можно было
бы и изменить). Существует также несколько необязательных полей изображений, в
которых администратор может загружать любые изображения, внедренные в HTML-
версию бюллетеня. Каждый из этих файлов должен указываться и загружаться от-
дельно.
718
Часть V. Реальные проекты на РНР и MySQL
Отображаемая форма аналогична обычной форме загрузки файлов, за исключе-
нием того, что в этом случае она используется для загрузки нескольких файлов. Это
обусловливает некоторые небольшие различия в синтаксисе формы и в способе об-
работки загруженных файлов на другом конце.
Код функции display_mail_form() показан в листинге 30.15.
Листинг 30.15. Функция display mail_form() из библиотеки output_fns.php —
эта функция выводит форму загрузки файлов
function display_mail__form( Semail, $listid=0)
{
// Выводит html-форму для загрузки нового сообщения
global $table_width;
$list = get_all_lists();
$lists = sizeof($list);
ctable cellpadding='4' cellspacing='0' border='0'
width='<?php echo $table_width?> '>
<form enctype='multipart/form-data' action='upload.php' method='post1>
<tr>
<td bgcolor="#cccccc">
Список:
c/td>
<td bgcolor=’’#cccccc">
<select name="list">
<?php
for($i = 0; $ic$lists; $i++)
{
echo '<option value = '.$list[$i][0];
if ($listid== $list[$i] [0]) echo ' selected';
echo ' >' . $list [$i] [1] . "c/option>\n” ;
}
</select>
</td>
</tr>
<tr>
<td bgcolor=''#cccccc">
Тема:
</td>
<td bgcolor="#cccccc">
cinput type=''text" name=”subject” value="<?php echo $subject?>”
size = 60 ></td>
</tr>
ctrxtd bgcolor=''#cccccc">
Текстовая версия:
</tdxtd bgcolor="#cccccc”>
cinput type=''file" name='userfile[0] ' size = 60>
</tdx/tr>
ctrxtd bgcolor="#cccccc">
HTML-версия:
</tdxtd bgcolor=” #cccccc">
cinput type="file" name='userfile[1] ' size = 60>
c/td></tr>
Глааа 30. Разработка диспетчера списков рассылки
719
<trxtd bgcolor="#cccccc" со1зрап="2">Изображения: (не обязательны)
<?php
$max .images = 10;
for($i = 0; $i<10; $i++)
{
echo "<trxtd bgcolor='ttcccccc'>Изображение ". ($i+l) . ' </td>';
echo "<td bgcolor='ttcccccc';
echo "<input type=\"file\" name='userfile[".($i+2)."]'
size=' 60 ' x/tdx/tr>" ;
}
<trxtd colspan='2' bgcolor=''#cccccc“ align= ' center' >
<input type="hidden" name="max_images" value=<?php echo $max_images?>>
<input type="hidden” name="listid" value=<?php echo $listid?>>
<?php display_form_button('upload-files'); ?>
</td>
</form>
</tr>
</table>
<?php
2
Следует отметить, что файлы, которые требуется загрузить, вводятся в набор тек-
стовых полей, каждое из которых имеет тип file, и будут иметь имена от userfile [0]
до userfile [п]. По сути, эти поля формы обрабатываются так же, как обрабатыва-
лись бы флажки, а их именование выполняется в соответствии с соглашением для
массивов.
Если с помощью PHP-сценария необходимо загрузить произвольное количество
файлов и просто обрабатывать их в виде массива, следует придерживаться этого со-
глашения.
Сценарий, обрабатывающий эту форму, фактически приведет к созданию трех
массивов. Давайте рассмотрим этот сценарий более подробно.
Обработка загрузки нескольких файлов
Вероятно, вы помните, что код загрузки файлов был помещен в отдельный файл.
Полный код этого файла upload. php можно найти в листинге 30.16.
Листинг 30.16. В сценарии upload.php реализована загрузка всех файлов,
необходимых для информационного бюллетеня
<?php
// Эта функциональность вынесена в отдельный файл, чтобы можно было
// проще решить вопросы безопасности.
// Если что-то идет не так, как ожидалось, осуществляется выход
$max_size = 50000;
include ('include_fns.php');
session_start();
// Загружать файлы могут только администраторы
iff!check_admin_user())
720
Часть V. Реальные проекты на РНР и MySQL
{
echo 'Вы не имеете права использовать эту страницу.';
exit ;
}
// Создать кнопки панели инструментов администрирования
Sbuttons = аггауО;
$buttons[0] = 'change-password';
Sbuttons[1] = 'create-list';
$buttons[2] = 'create-mail';
$buttons[3] = 'view-mail';
$buttons[4] = 'log-out';
$buttons[5] = 'show-all-lists';
Sbuttons[6] = 'show-my-lists';
$buttons[7] = 'show-other-lists';
do_html_header('Пирамида - Загрузка файлов');
display_toolbar($buttons);
// Проверить, что страница вызывается с обязательными данными
if(!$_FILES['userfile']['name'][0]
||!$_FILES['userfile']['name'][1]
| !$_POST['subject']||!$_POST['list'])
{
echo 'Ошибка; вы не заполнили все поля формы.
Необязательными полями являются только поля изображений.
Каждое сообщение должно снабжаться темой, а также иметь
текстовую и HTML-версию содержимого.';
do_html_footer();
exit;
}
$list = $_POST['list'];
$subject = $_POST['subject'];
if(!$conn=db_connect())
{
echo '<р>Невозможно подключиться к базе данных</р>';
do_html_footer();
exit;
}
// Сохранить детали сообщения в базе данных
$query = "insert into mail values (NULL,
'”.$_SESSION['admin_user
'”.Ssubject."',
' " .$list."',
'STORED', NULL, NULL)";
$result = $conn->query(Squery);
if(!Sresult)
{
do_html_footer();
exit;
}
Глава 30. Разработка диспетчера списков рассылки
721
// Получить идентификатор сообщения, присвоенный MySQL
Smailid = $conn->insert_id;
if(!Smailid)
{
do_html_footer();
exit ;
}
// Создание каталога завершится неудачей, если это сообщение не является
// первым успешно заархивированным сообщением
@ mkdir('archive/'.Slist, 0700);
// Если создание определенного каталога для данного списка рассылки
// потерпело неудачу, то это проблема
if(imkdir('archive/'.Slist."/Smailid", 0700))
{
do_html_footer();
exit;
}
// Выполнить проход по всему массиву имен загружаемых файлов
$i = 0;
while ($_FILES['userfile']['name'][$i]&&
$_FILES['userfile']['tmp_name'][$i]!='none')
{
echo '<р>3агрузка '.$_F1LES['userfile']['name'][$i].’ - ’;
echo $_FILES[’userfile']['size'][$i].' байт.</р>';
if ($_FILES)'userfile'] ['size'] [$i]==0)
{
echo 'Ошибка: '. $_FILES ['user file']['name'][ $i] .
' имеет нулевую длину';
$i++;
continue;
}
if ($_FILES)'userfile']['size'][$i]>$max_size)
{
echo 'Ошибка: '.$_FILES['userfile']['name'][$i]
.' имеет длину более '.$max_size.' байт';
$i++;
continue;
}
// Весьма желательно проверить, что загружаемое изображение
// действительно является изображением.
// Если функция getimagesize)) может работать с его размерами,
// возможно, это таки изображение.
if($i>l&&!getimagesize($_FILES['userfile’]['tmp_name’][$i]))
{
echo 'Ошибка: '.$_FILES['userfile']['name'][$i].
' поврежден либо не является gif-, jpeg- или png-изображением';
$i++;
continue;
}
722
Часть V. Реальные проекты на РНР и MySQL
// Файл 0 (сообщение в текстовом формате) и файл 1
// (сообщение в html-формате) обрабатываются как специальные случаи
if($i == 0)
$destination = "archive/$list/$mailid/text.txt";
else if($i == 1)
$destination = "archive/$list/$mailid/index.html”;
else
{
$destination = ”archive/$list/$mailid/"
.$_FILES['userfile']['name'][$ i];
$query = "insert into images
values ($mailid,
'”.addslashes($_FILES['userfile']['name'][$ i]) . ” ' ,
’".addslashes($_FILES['userfile']['type'][$i])."')";
$result = $conn->query($query);
}
// Если используется версия PHP >= 4.03
if (! is_uploaded_file ($._FILES [ 'userfile' ] [' tmp_name ' ] [$i]))
{
// Возможно, осуществляется атака типа загрузки файла
echo 'Что-то интересное происходит с '
.$_FILES['userfile']['name']
.'; его загрузка не выполняется.';
do_html_footer();
exit;
}
move_uploaded_file($_FILES['userfile’]['tmp_name'][$i],
$destination);
// Если используется версия PHP <= 4.02
copy ($userfile[$i], Sdestination);
unlink($userfile[$ i]);
$i++;
}
display_preview_buttcn($list, $mailid, 'preview-html');
display_preview_button($list, $mailid, 'preview-text’);
display_button('send', "&id=$mailid”);
echo '<br /xbr /xbr /xbr /xbr />';
do_html_footer();
Давайте постепенно разберем все действия, обрабатываемые в коде листин-
га 30.16. Прежде всего, мы начинаем сеанс и проверяем, что пользователь вошел как
администратор — мы вовсе не хотим, чтобы кто-то другой мог загружать файлы.
Строго говоря, следовало бы проверить также переменные list и mailid на
предмет наличия недопустимых символов, но ради краткости мы опускаем эти
действия.
Глава 30. Разработка диспетчера списков рассылки
723
Затем мы создаем и отправляем заголовки страницы и проверяем корректность
заполнения формы. В данном случае это важно, поскольку форма достаточно сложна
для заполнения.
Далее мы создаем в базе данных запись для этого сообщения и создаем каталог в
архиве, в котором будет храниться сообщение.
Затем дело доходит до основной части сценария, в которой выполняется провер-
ка и перемещение каждого из загруженных файлов. Эта часть отличается для случая
загрузки нескольких файлов. Сейчас мы имеем дело с четырьмя массивами:
$_FILES['userfile ' I ['name'], $ „.FILES['userfile ' ] [ 'tmp_name’], $_FILES[
'userfile']['size'], $_FILES]'userfile']]'type']. Перечисленные массивы со-
ответствуют эквивалентам с аналогичными именами, которые встречались при за-
грузке одного файла, за исключением того, что теперь каждый из них является мас-
сивом.
Детальная информация о первом файле в форме будет содержаться в элемен-
тах $_FILES[ ' userfile'][1tmp_name'] [01, $_FILES[ 'userfile'] [ 'name'] [0],
$_FILES['userfile']['size'] [0] и$_FILES['userfile'] ['type'] [0].
Имея эти четыре массива, мы выполняем обычные проверки для целей безопас-
ности и затем перемещаем файлы в архив.
В заключение мы предоставляем администратору набор кнопок, которые он мо-
жет использовать для просмотра загруженного информационного бюллетеня перед
его отправкой, и кнопку для отправки бюллетеня. Вывод, генерируемый сценарием
upload.php, показан на рис. 30.13.
Рис. 30.13. Сценарий загрузки сообщает имена и размеры загружен-
ных файлов
724
Часть V. Реальные проекты на РНР и MySQL
Предварительный просмотр
информационного бюллетеня
Администратору доступны два способа предварительного просмотра информаци-
онного бюллетеня перед его отправкой в список рассылки. Он может обратиться к
функциям предварительного просмотра на странице загрузки, если желает выпол-
нить предварительный просмотр немедленно после загрузки файлов. Если же он хо-
чет просмотреть и отправить сообщение позже, то может также щелкнуть на кнопке
View Mail (Просмотреть сообщение), в результате чего отобразятся все неотправлен-
ные информационные бюллетени, которые хранятся в системе. Кнопка View Mail
активизирует действие view-mail, которое обрабатывается следующим кодом:
case 'view-mail' :
display_items('Неотправленные сообщения', get_unsent_mail(get_email()),
'preview-html', 'preview-text', 'send');
break;
}
Как видите, это действие также использует функцию display_items (), связанную с
выводом кнопок, реализующих действия preview-html, preview-text и send.
Интересно отметить, что кнопки предварительного просмотра в действительно-
сти не запускают некоторое действие, а вместо этого устанавливают связь непосред-
ственно с информационным бюллетенем в архиве. Если вы снова посмотрите на лис-
тинги 30.7 и 30.16, то увидите, что для создания этих кнопок используется функция
display_preview_button (), а не display_button (). Функция display_button () созда-
ет ссылку' с изображением на некоторый сценарий с GET-параметрами, если они необхо-
димы, тогда как функция display_preview_button () определяет простую ссылку' внутрь
архива. Щелчок на этой ссылке приведет к отображению нового окна, что достигается
с помощью атрибута target=new HTML-дескриптора привязки. Результат предвари-
тельного просмотра HTML-версии информационного бюллетеня показан на рис. 30.14.
Рис. 30.14. Просмотр информационного бюллетеня
в HTML-формате, дополненного изображениями
Глава 30. Разработка диспетчера списков рассылки
725
Отправка сообщения
Щелчок на кнопке Send (Отправить) для информационного сообщения активи-
зирует действие send, которое приводит к выполнению следующего кода:
case 'send' :
{
send($_GET[1 id'], $_SESSION['admin_user' ]) ;
break;
}
При обработке этого действия вызывается функция send (), которая входит в со-
став библиотеки mlm_fns.php. Это довольно-таки большая функция. Кроме того,
именно в ней используется класс Mail_mime.
Код этой функции можно найти в листинге 30.17.
Листинг 30.17. Функция send () из библиотеки nlm_fns .php — эта функция
окончательно отправляет информационный бюллетень
function send($mailid, $admin_user)
// Создает сообщение на основе хранящихся в базе данных записей и файлов.
// Отправляет тестовые сообщения администратору или реальные сообщения
// в список рассылки
{
if(!check_admin_user($admin_user))
return false;
if(!($info = load_mail_info(Smailid)))
{
echo "He удается загрузить информацию о списке для сообщения $mailid";
return false;
)
$subject = $info['subject'];
Slistid = $info['listid'];
Sstatus = $info['status'];
Ssent = $info['sent'];
$from_name = 'Пирамида';
$from_address = 'return@address';
Squery = “select email from sub_lists where listid = Slistid”;
Sconn = db_connect();
Sresult = $conn->query(Squery);
if (!Sresult)
{
echo Squery;
return false;
)
else if ($result->num_rows==0)
{
echo "Отсутствуют подписчики на список с идентификатором Slistid”;
return false;
)
726
Часть V. Реальные проекты на РНР и MySQL
// Включить почтовые классы библиотеки PEAR
require('Mail.php');
require('Mail/mime.php');
// Создать экземпляр класса Mail_mime и передать ему комбинацию символов
// возврат каретки/перевод строки, которые используются в данной системе
Smessage = new Mail_mime("\r\n");
// Прочитать текстовую версию сообщения
Stextfilename = "archive/$listid/$mailid/text.txt";
$tfp = fopen(Stextfilename, "r“);
$text = fread($tfp, filesize(Stextfilename)) ,-
fclose($ tfp);
// Прочитать HTML-версию сообщения
Shtmlfilename = "archive/$listid/$mailid/index.html";
$hfp = fopen(Shtmlfilename, "r");
$html = fread($hfp, filesize($htmlfilename));
fclose($hfp);
// Добавить HTML- и текстовое содержимое в объект типа Mail_mime
$message->setTXTBody($text);
$message->setHTMLBody(Shtml) ;
// Получить список изображений, связанных с данным сообщением
Squery = "select path, mimetype from images where mailid = Smailid";
Sresult = $conn->query(Squery);
if(!Sresult)
(
echo '<p>He удается извлечь список изображений из базы данных.</р>';
return false;
}
$num = $result->num_rows;
for($i = 0; $i<$num; $i++)
{
// Загрузить с лиска каждое изображение
$row = $result->fetch_array();
Simgfilename = "archive/$listid/$mailid/" .$row[0j ,-
Simgtype = $row[l];
// Добавить каждое изображение к объекту Mail_mime
$message->addHTMLlmage(Simgfilename, Simgtype, Simgfilename, true);
)
11 Создать тело сообщения
$body = $message->get();
// Создать заголовки сообщения
$from = '"'.get_real_name($admin_user).'" <’.$admin_user.'>';
Shdrarray = array(’From' => $from,
'Subject' => Ssubject);
Shdrs = $message->headers(Shdrarray);
// Создать объект отправителя
Ssender =& Mail::factory('mail');
Глава 30. Разработка диспетчера списков рассылки
727
if($status == 'STORED')
{
// Отправить HTML-версию сообщения администратору
$sender->send($admin_user, $hdrs, Jbody);
// Отправить текстовую версию сообщения администратору
mail($admin_user, Ssubject, $text, 'From: "'
.get_real_name($admin_user).'" <'.$admin_user
echo "Сообщение отправлено $admin_user";
// Пометить сообщение как проверенное
Squery = "update mail set status = 'TESTED' where mailid = $mailid";
Sresult = $conn->query(Squery);
echo '<р>Чтобы отправить сообщение в список рассылки,
щелкните еще раз на кнопке отправки. <center>';
display_button('send', "&id=$mailid");
echo ' </centerx/p>' ;
}
else if($status == 'TESTED')
{
// Отправить сообщение в список рассылки
Squery = "select subscribers.realname, sub_lists.email,
subscribers.mimetype
from sub_lists, subscribers
where listid = Slistid and
sub_lists.email = subscribers.email";
Sresult = $conn->query(Squery);
if (!Sresult)
echo '<р>Ошибка при чтении списка подписчиков</р>';
Scount = 0;
// Для каждого подписчика
while(Jsubscriber = $result->fetch_row())
{
if($subscriber[2]=='H')
{
// Отправить HTML-версию всем желающим подписчикам
$sender->send($subscriber[1], $hdrs, Jbody);
}
else
{
// Отправить текстовую версию подписчикам,
// которые не желают иметь дело с HTML
mail(Jsubscriber[1], Ssubject, Jtext,
'From: "'.get_real_name($admin_user).'" <'.$admin_user.">");
}
$count++;
}
Squery = "update mail set status = 'SENT', sent = now()
where mailid = Smailid";
Sresult = $conn->query(Squery);
728
Часть V. Реальные проекты на РНР и MySQL
echo "<р>Общее количество отправленных сообщений: Scount.</р>";
}
else if($status == 'SENT')
{
echo ’<р>Это сообщение уже было отправлено.</р>';
}
}
Эта функция осуществляет несколько различных действий. Она выполняет тесто-
вую отправку информационного бюллетеня администратору, прежде чем отправлять
его в список рассылки. Функция управляет этим процессом, отслеживая состояние
фрагмента сообщения в базе данных. Когда сценарий загрузки загружает фрагмент
сообщения, он устанавливает начальное состояние этого сообщения равным
"STORED"(сохранено).
Если функция send () обнаруживает, что для сообщения установлено состояние
"STORED", она обновляет его до "TESTED" (проверено) и отправляет его администра-
тору. Состояние "TESTED" означает, что информационный бюллетень прошел тести-
рование за счет отправки его администратору. Если состоянием является "TESTED",
оно изменяется на "SENT" (отправлено), и сообщение будет отправлено всему списку
рассылки. Это означает, что фактически каждый фрагмент сообщения должен быть
отправлен дважды: один раз в тестовом режиме и один раз в реальном.
Функция также отправляет два различных вида сообщений электронной почты:
текстовую версию, которая отправляется при помощи PHP-функции mail (), и HTML-
версию, которая отправляется с помощью класса Mail_mime. Функция mail!) уже
много раз использовалась в этой книге, поэтому давайте рассмотрим, что собой пред-
ставляет класс Mail_mime. Мы не будем освещать этот класс в полном объеме, однако
поясним, как он применяется в этом довольно-таки типовом приложении.
Все начинается с включения файлов класса и создания экземпляра класса
Mail_mime:
// Включить почтовые классы библиотеки PEAR
require!'Mail.php') ;
require!'Mail/mime.php');
// Создать экземпляр класса Mail_mime и передать ему комбинацию символов
// возврат каретки/перевод строки, которые используются в данной системе
Smessage = new Mail_mime("\r\n") ;
Вы наверняка заметили, что было включено два файла класса. Обобщенный класс
Mail из библиотеки PEAR будет использоваться позже в этом сценарии, когда мы бу-
дем выполнять собственно отправку сообщений. Этот класс входит в состав стан-
дартной поставки библиотеки PEAR.
Класс Mail_mime служит для создания сообщения с MIME-форматами, которые за-
тем отправляются.
Следующий шаг заключается в чтении текстовой и HTML-версий сообщения и по-
следующем их добавлении к объекту Mail_mime:
// Прочитать текстовую версию сообщения
Stextfilename = “archive/Slistid/$mailid/text.txt";
$tfp = fopen($textfilename, "r");
Глава 30. Разработка диспетчера списков рассылки
729
Stext = fread($tfp, filesize($textfilename));
fclose($tfp);
// Прочитать HTML-версию сообщения
Shtmlfilename = "archive/Slistid/Smailid/index.html”;
$hfp = fopen($htmlfilename, "r");
$html = fread($hfp, filesize(Shtmlfilename));
fclose($hfp);
// Добавить HTML- и текстовое содержимое в объект типа Mail_mime
$message->setTXTBody($text);
$message->setHTMLBody($html);
Затем мы загружаем данные об изображениях из базы данных и просматриваем их
в цикле, добавляя каждое изображение к сообщению, которое требуется отправить:
$num = $result->num_rows;
for($i = 0; $i<$nwn; $i++)
{
// Загрузить с диска каждое изображение
$row = $result->fetch_array();
Simgfilename = "archive/Slistid/Smailid/".$row[0];
Simgtype = $row[l];
// Добавить каждое изображение к объекту Mail_mime
$message->addHTMLlmage($imgfilename, Simgtype, Simgfilename, true);
}
В число параметров, передаваемых функции add_html_image (), входит имя файла
с изображением (либо же можно было бы передать собственно считанные данные
изображения), MIME-тип изображения, еще раз имя файла, а также значение true,
которое будет говорить о том, что в первом параметре передается именно имя файла,
а не данные изображения. (Если вы хотите иметь дело с данными изображения, пере-
давайте в качестве параметров сами данные, соответствующий MIME-тип, пустой па-
раметр и, наконец, значение false.) Вообще говоря, эти параметры могут слегка за-
путать.
На этом этапе мы должны создать тело сообщения, причем это делается до того,
как можно будет формировать заголовки сообщения. Вот как выглядит код создания
тела сообщения:
// Создать тело сообщения
Sbody = $message->get();
Затем можно сформировать и заголовки сообщения, обратившись к функции
headers!) объекта Mail_mime:
// Создать заголовки сообщения
$from = ’"'.get_real_name($admin_user).’" <’.$admin_user.'>';
Shdrarray = array('From' => $from,
'Subject' => Ssubject);
Shdrs = $message->headers(Shdrarray);
Наконец, имея созданное тело сообщения, его можно и отправить. Для того что-
бы отправить сообщение, необходимо создать экземпляр класса Mail из библиотеки
730
Часть V. Реальные проекты на РНР и MySQL
PEAR и передать ему созданное ранее сообщение. Мы начинаем с создания экземпля-
ра класса Mail:
// Создать объект отправителя
$sender =& Mail::factory!'mail');
(Присутствующий в вызове параметр 'mail' просто заставляет экземпляр класса
Mail использовать для отправки сообщений PHP-функцию mail О. Другими значе-
ниями этого параметра являются ' sendmail' и ' smtp', которые приводят к очевид-
ным результатам.)
Следующим шагом будет отправка сообщений подписчикам. Это делается путем
извлечения и циклической обработки каждого из пользователей, подписавшихся на
данный список рассылки, и применения либо функции send!) объекта Mail, либо
обычной функции mail (), в зависимости от MIME-типа, предпочитаемого пользова-
телем:
if($subscriber[2]=='Н')
{
// Отправить HTML-версию всем желающим подписчикам
$sender->send($subscriber[1], $hdrs, $body);
}
else
{
// Отправить текстовую версию подписчикам,
// которые не желают иметь дело с HTML
mail($subscriber[1], $subject, $text,
'From: "' .get_real_name($admin_user) . '" <’. $admin_user
В первом параметре $sender->send () должен передаваться адрес электронной
почты пользователя, во втором — заголовки сообщения и в третьем — тело сообщения.
Вот и все! Разработка приложения диспетчера списков рассылки благополучно за-
вершена.
Расширение проекта
Как обычно случается с проектами подобного рода, существует множество путей
расширения его функциональных возможностей. Вот что, вполне возможно, может
п отребоваться:
Подтверждение членства со стороны подписчиков, чтобы пользователя нельзя
было подписать на список рассылки без его согласия. Обычно это делается за
счет отправки сообщений электронной почты по их учетным записям и удале-
ния тех из них, от которых не поступил ответ. Такой подход будет обеспечи-
вать также удаление из базы данных любых неверно записанных адресов элек-
тронной почты.
Предоставление администратору права утверждать или отклонять пользовате-
лей, которые желают подписаться на списки рассылки.
Добавить функциональные возможности открытого списка рассылки, которые
позволяют любому члену отправлять сообщение в список.
Глава 30. Разработка диспетчера списков рассылки
731
Позволять только зарегистрированным членам просматривать архив конкрет-
ного списка рассылки.
Предоставить пользователям возможность искать списки рассылки, которые
соответствуют определенным критериям. Например, пользователей могут ин-
тересовать информационные бюллетени, касающиеся игры в теннис. Как
только количество информационных бюллетеней начинает превосходить не-
который заданный размер, функция поиска могла бы пригодиться для отыска-
ния конкретных бюллетеней.
Увеличение эффективности приложения при обработке больших списков рас-
сылки. Для таких целей более предпочтительным может оказаться узкоспециа-
лизированный диспетчер списков рассылки наподобие ezmlm, в котором осу-
ществляется очередизация и отправка сообщений многопоточным способом.
Многократные вызовы функции mail () в РНР существенно снижают эффек-
тивность, поэтому РНР не очень хорошо подходит для приложений, имеющих
дело с крупными списками рассылки. Разумеется, пользовательский интерфейс
можно разрабатывать и на РНР, однако для собственно диспетчеризации от-
правки сообщений и списков рассылки лучше воспользоваться ezmlm.
Что дальше
В следующей главе мы реализуем приложение поддержки Web-форума, которое
даст пользователям возможность вести онлайновые дискуссии, структурированные
по темам и цепочкам бесед.
732
Часть V. Реальные проекты на РНР и MySQL
31
Разработка приложения
поддержки Web-форумов
Один из очень эффективных способов привлечения пользователей к сайту пред-
полагает организацию на нем Web-форума. Форумы могут использоваться для
достижения разнообразных целей, от поддержки дискуссионных групп по различным
философским вопросам до технической поддержки продуктов, выпускаемых компа-
нией. В рамках этой главы мы разработаем функциональность Web-форума, исполь-
зуя РНР. В качестве альтернативы для создания своих форумов можно задействовать
один из существующих пакетов наподобие Phorum или phpBB.
Иногда на Web-форумы ссылаются также как на дискуссионные трибуны или дискуссион-
ные группы. Идея форума состоит в том, чтобы одни пользователи могли отправлять в них
статьи или вопросы, а другие пользователи могли просматривать эти вопросы и отвечать
на них. Каждая тема дискуссии в форуме называется цепочкой (thread).
Мы реализуем Web-форум с названием “Поговорим ни о чем” (“blah-blah”), кото-
рый предоставит пользователям возможность выполнять следующие действия:
Начинать новые цепочки дискуссий, отправляя статьи.
Отправлять статьи в ответ на существующие статьи.
Просматривать статьи, которые уже были отправлены в форум.
Просматривать цепочки беседы в форуме.
Просматривать взаимосвязь между статьями, то есть видеть, какие статьи яв-
ляются ответами на другие статьи.
Задача
Создание форума — действительно интересная задача. Нам потребуется отыскать
какой-нибудь способ хранения статей в базе данных с записью информации об авторе,
заголовке и содержимом статьи. На первый взгляд, такая база данных может казаться
не слишком отличающейся от базы данных книжного магазина “Буквофил”.
Однако особенность работы большинства программ тематических дискуссий со-
стоит в том, что наряду с отображением доступных статей они отображают и взаимо-
связь между статьями. Другими словами, пользователь может видеть, какие статьи
являются ответами на другие статьи (и за какой статьей они следуют), а какие начи-
нают новые темы обсуждения.
Примеры дискуссионных форумов, реализующих упомянутые функциональные
возможности, можно найти на очень многих сайтах, в том числе и на сайте Slashdot
по адресу:
http://slashdot.org/
Способ отображения этих взаимосвязей необходимо самым тщательным обра-
зом продумать. Пользователь должен иметь возможность просматривать отдельное
сообщение, цепочку беседы с показанными взаимосвязями, а также все цепочки в
системе.
Пользователи должны также иметь возможность отправлять статьи по новым те-
мам или отвечать на существующие статьи. Это достаточно простая часть функцио-
нальности приложения.
Компоненты решения
Ранее мы уже упоминали, что сохранение и получение информации об авторе и
текста сообщения особых сложностей не представляет.
Наиболее трудная часть этого приложения связана с выбором структуры базы
данных, которая будет хранить требуемую информацию, а также способа эффектив-
ного навигации в рамках этой структуры.
Структура статей в дискуссии может выглядеть так, как показано на рис. 31.1.
Рис. 31.1. Статья в тематической дискуссии может быть пер-
вой статьей, начинающей новую тему, однако, как правило,
она представляет собой ответ на какую-то другую статью
На этой блок-схеме легко заметить, что имеется первоначальная статья, с которой
начинается новая тема, а также три ответа на нее. С некоторыми из этих ответов
также связаны ответы. В свою очередь, эти ответы могли бы также иметь ответы, и
так почти до бесконечности (или до полного исчерпания темы).
Блок-схема дает ключ к тому, как хранить и получать данные о статьях и связях
между ними. Блок-схема представляет ни что иное, как древовидную структуру. Если вы
обладаете достаточным опытом программирования, то наверняка знаете, что древо-
видная структура является одной из наиболее важных из применяемых структур дан-
ных. Такая структура имеет узлы (они же статьи) и связи (они же отношения между
734
Часть V. Реальные проекты на РНР и MySQL
статьями) — в общем, все как в классическом обобщенном дереве. (Если вы вообще не
знакомы с использованием деревьев в качестве структур данных, не стоит особо бес-
покоиться — в процессе изложения материала мы достаточно подробно рассмотрим
их основные свойства.)
Чтобы это все как-то заработало, потребуется решить следующие две основные за-
дачи:
1. Найти способ отображения этой древовидной структуры на хранилище данных
(в нашем конкретном случае — на базу данных MySQL).
2. Найти способ восстановления данных при необходимости.
В рамках этого проекта мы начнем с реализации базы данных MySQL, которая по-
зволит хранить статьи в промежутках между их использованием. Кроме того, мы соз-
дадим простые интерфейсы, которые позволят сохранять статьи.
При загрузке списка статей, предназначенных для просмотра, заголовки всех
статей будут загружаться в PHP-класс с именем treenode. Каждый объект класса
treenode будет содержать заголовки статьи и набор ответов на ту или иную статью.
Ответы будут храниться в массиве. Каждый ответ сам по себе будет объектом
treenode, который может содержать массив ответов на данную статью, которые и
сами являются объектами treenode, и так далее. Это продолжается вплоть до так на-
зываемых узлов-листьев (leaf nodes) дерева — узлов, которые не содержат никаких от-
ветов. В результате образуется древовидная структура, которая выглядит приблизи-
тельно так, как показано на рис. 31.1.
А сейчас мы дадим определения некоторых терминов. Сообщение, на которое
осуществляется ответ, будем называть родительским узлом (parent node) текущего узла.
Любые ответы на сообщение будем называть дочерними узлами (children) текущего уз-
ла. Проще всего это запомнить, если думать о древовидной структуре как о семейном
генеалогическом древе.
Первую статью этой древовидной структуры — ту, что не имеет родительского уз-
ла — иногда называют корневым узлом (root node).
Примечание
Не вполне интуитивным может показаться тот факт, что обычно корнеаой узел помещают в
верхнюю часть диаграммы, в отличие от деревьев в живой природе.
Для построения и отображения этой древовидной структуры в рамках данного про-
екта мы подготовим набор рекурсивных функций. (Рекурсия обсуждалась в главе 5.)
Для реализации этой структуры мы решили использовать класс, поскольку это
простейший способ построения сложной динамически расширяемой структуры дан-
ных для данного приложения. Это также означает, что мы получим исключительно
простой и элегантный код для выполнения относительно сложных действий.
Обзор решения
Чтобы в действительности понять, что было сделано для этого проекта, возмож-
но, имеет смысл подробно ознакомиться с кодом, что мы и предпримем, правда, чуть
позже. Хотя это приложение несколько менее громоздко по сравнению с другими,
зато его код намного сложнее.
Глааа 31. Разработка приложения поддержки Web-форумов
735
Приложение содержит всего три реальных страницы. Существует основная ин-
дексная страница, на которой отображаются все статьи из форума в виде ссылок на
статьи. На этой странице можно добавлять новые статьи, просматривать перечис-
ленные статьи или изменять способ просмотра статей, раскрывая или сворачивая
ветви дерева. (Подробнее об этом — ниже.) На странице представления статьи можно
отправлять ответ на эту статью или просматривать существующие ответы на нее.
Страница создания новой статьи позволяет ввести новую статью — будь то ответ на
существующее сообщение или же новое, ни с чем не связанное сообщение.
Блок-схема системы показана на рис. 31.2.
Рис. 31.2. Три основных части системы форума “По-
говорим ни о чем”
Перечень и краткие описания файлов этого приложения можно найти в табл. 31.1.
Таблица 31.1. Файлы приложения Web-форума
Имя файла Tun Описание
index.php Приложение Главная страница, которую пользователи будут видеть после входа на сайт. Содержит раскрываемый и свер- тываемый список всех статей, доступных на сайте.
new_post.php Приложение Форма, используемая для отправки новых статей.
store_new_post.php Приложение Страница, на которой сохраняются статьи, введенные в форме new_po s t. php.
view_pos t.php Приложение Страница, на которой отображается отдельное сооб- щение и список ответов на это сообщение.
treenode_class.php Библиотека Файл, содержащий код класса treenode, который будет использоваться для отображения иерархии статей.
include_fns.php Библиотека Собирает воедино все остальные библиотеки функ- ций (другие перечисленные в этой таблице файлы библиотечного типа), необходимые для целей при- ложения.
data_valid_fns.php Библиотека Коллекция функций проверки допустимости данных.
db_fns. php Библиотека Коллекция функций подключения к базе данных.
discussion_fns.php Библиотека Коллекция функций для сохранения и выборки статей.
outpu t_fns.php Библиотека Коллекция функций для вывода HTML- содержимого.
create_database.sql SQL SQL-код для создания базы данных, необходимой для приложения.
А теперь давайте подробно рассмотрим реализацию.
736
Часть V. Реальные проекты на РНР и MySQL
Создание базы данных
Для каждой статьи, которая была отправлена в форум, мы должны хранить несколько
атрибутов: лицо, которое ее написало (назовем его отправителем)', заголовок статьи; вре-
мя ее отправки; тело статьи. Следовательно, нам потребуется иметь таблицу статей. Для
каждой статьи будет сгенерирован уникальный идентификатор post id.
Каждая статья должна содержать некоторую информацию о ее месте в иерархии.
Информацию о дочерних статьях каждой статьи можно было бы хранить вместе с ней.
Однако с каждой статьей может быть связано сразу несколько ответов, поэтому такой
подход может привести к определенным проблемам с выбором структуры базы данных.
Поскольку каждая статья может быть ответом только на одну другую статью, проще
хранить ссылку на родительскую статью, то есть статью, на которую отвечает данная.
При таком подходе для каждой статьи потребуется хранить следующие данные:
postid — уникальный идентификатор статьи.
parent — идентификатор postid родительской статьи.
poster — автор статьи.
title — заголовок статьи.
posted — дата и время отправки статьи.
message — тело статьи.
В отношении перечисленных выше полей мы еще проведем некоторую опти-
мизацию.
При попытке определить, имеет ли статья какие-либо ответы не нее, необходимо
выполнить запрос на предмет наличия каких-либо других статей, для которых данная
статья является родительской. Подобного рода информация потребуется для каждой
статьи, представленной в списке. Чем меньше запросов придется выполнять, тем бы-
стрее будет работать код. Избавиться от потребности в этих запросах можно, добавив
поле, указывающее на наличие хотя бы одного ответа. Давайте назовем его children
и установим для него булевский тип: это поле будет принимать значение 1, если узел
имеет дочерние узлы, и 0 — если нет.
Оптимизация никогда не достается бесплатно. В данном случае мы вынуждены
хранить избыточные данные. Поскольку данные сохраняются двумя способами, не-
обходимо обеспечить, чтобы оба представления были согласованы одно с другим.
При добавлении дочернего узла необходимо обновлять родительский узел. Если мы
разрешаем удаление дочернего узла, для обеспечения ссылочной целостности базы
данных также потребуется обновлять и родительский узел. В данном проекте мы не
собираемся создавать средства для удаления статей, поэтому решения потребует
только одна часть проблемы. Если же вдруг будет принято решение расширить этот
код, то указанное обстоятельство потребуется принять во внимание.
Существуег еще одна оп тимизация, которую мы выполним в рамках этого проекта.
Тела сообщений будут отделены от остальных данных, и храниться в отдельной таб-
лице. Это связано с тем, что этот атрибут будет иметь MySQL-тип text. Наличие это-
го типа в таблице может замедлить выполнение запросов к данной таблице. Посколь-
ку для построения древовидной структуры потребуется выполнение множества
небольших запросов, это может привести к существенному замедлению работы. Ко-
Глава 31. Разработка приложения поддержки Web-форумов
737
гда же тела сообщений хранятся в отдельной таблице, их можно получать только то-
гда, когда пользователь желает просмотреть конкретное сообщение.
MySQL выполняет поиск записей фиксированного размера быстрее, чем поиск
записей переменного размера. При необходимости использования данных перемен-
ного размера производительность можно увеличить, организовав индексы по полям,
которые будут задействоваться во время поиска в базе данных. Для некоторых проек-
тов имело бы смысл оставить текстовое поле в той же записи, что и остальные дан-
ные, и определить индексы для всех столбцов, по которым планируется выполнять
поиск. Тем не менее, учитывая тот факт, что генерация индексов требует времени, а
данные в форумах, скорее всего, будут изменяться постоянно, мы может столкнуться
с необходимостью выполнения частых генераций индексов.
Кроме того, мы решили добавить также атрибут area на случай, если мы впослед-
ствии решим реализовать с помощью одного приложения сразу несколько бесед.
В рамках данного проекта эта возможность не будет реализована, однако имеет смысл
все же зарезервировать ее на будущее.
С учетом изложенных выше соображений написан SQL-код создания базы данных
форума, который можно видеть в листинге 31.1.
Листинг 31.1. create_database. sql — SQL-код, создающий базу данных discussion
для хранения информации о дискуссиях
create database discussion;
use discussion;
create table header
(
parent int not null,
poster char(20) not null,
title char(20) not null,
children int default 0 not null,
area int default 1 not null,
posted datetime not null,
postid int unsigned not null auto_increment primary key
);
create table body
(
postid int unsigned not null primary key,
message text
) ;
grant select, insert, update, delete
on discussion.*
to discussion@localhost identified by 'password';
Эту структуру базы данных можно создать, запустив на выполнение приведенный
сценарий в среде MySQL:
mysql -u root -р < create_database.sql
При этом необходимо будет ввести пароль привилегированного пользователя
root. Вероятно, потребуется также изменить пароль, определенный для пользователя
дискуссии, на что-то более подходящее.
738
Часть V. Реальные проекты на РНР и MySQL
Для более полного понимания, как эта структура будет хранить статьи и их взаи-
мосвязи, посмотрите на рис. 31.3.
Представление
в виде базы данных
Представление
в виде дерева
Рис. 31.3. База данных хранит древовидную струк-
туру в плоской реляционной форме
Как видите, поле parent каждой статьи в базе данных содержит идентификатор
post id родительской статьи, которая расположена в дереве над ней. Родительская
статья — это статья, на которую дается ответ.
На рис. 31.3 должно быть заметно, что корневой узел с postid равным 1 не имеет
родительского узла. Все новые темы дискуссий будут располагаться в этой позиции.
Для статей такого типа их родительская статья (поле parent) представляется в базе
данных нулевым значением (0).
Просмотр дерева статей
Теперь нам необходимо выбрать способ извлечения информации из базы данных
и представления ее снова в виде древовидной структуры. Это будет выполнено через
сценарий главной страницы index.php. Для иллюстрации излагаемого материала
мы ввели несколько примеров статей при помощи сценариев отправки статей
new_post.php и store_new_post.php. Эти примеры будут рассмотрены в следующем
разделе.
Вначале мы обсудим проблемы вывода списка статей, поскольку именно он служит
основой сайта. После этого знакомство со всеми остальными компонентами большо-
го труда не составит.
Первоначальное представление статей, которое увидит пользователь, показа-
но на рис. 31.4.
Все статьи, представленные на этом рисунке, являются начальными. Ни одна из
них не является ответом на какую-то статью; все они — первые статьи по той или
иной конкретной теме.
Как видите, пользователю доступен набор опций. В окне имеется панель меню,
которая позволяет добавлять новую статью и раскрывать или сворачивать представ-
ление статей.
Чтобы понять значение этого, давайте внимательно посмотрим на статьи. Рядом с
некоторыми из них присутствуют символы плюса. Это означает, что на них были полу-
чены ответы. Чтобы увидеть ответы на конкретную статью, необходимо щелкнуть на
символе плюса. Результат щелчка на одном из этих символов можно видеть на рис. 31.5.
Глава 31. Разработка приложения поддержки Web-форумов
739
Рис. 31.4. Первоначальное представление списка
статей отображает статьи в “свернутой” форме
Рис. 31.5. Развернутая цепочка дискуссии на тему
устойчивости (“persistence”)
Несложно заметить, что щелчок на символе плюса привел к отображению ответов
на конкретную первоначальную статью. Теперь символ плюса превратился в символ
минуса. Если щелкнуть на нем, все статьи в цепочке окажутся свернутыми, и мы вновь
приходим к исходному представлению.
Читатели наверняка заметили также, что и рядом с одним из ответов присутствует
символ плюса. Это означает, что на данный ответ существуют свои ответы. Цепочка
ответов может иметь произвольную глубину, и каждый набор ответов можно про-
смотреть, щелкнув на соответствующем символе плюса.
740
Часть V. Реальные проекты на РНР и MySQL
Две кнопки панели меню, Развернуть и Свернуть, будут, соответственно, развора-
чивать и сворачивать любые возможные цепочки. Результат выполнения щелчка на
кнопке Развернуть показан на рис. 31.6.
Рис. 31.6. Теперь развернуты все цепочки
Если внимательно присмотреться к рисункам 31.5 и 31.6, можно заметить, что в
URL-строке некоторые параметры передаются обратно в файл index.php. На рис.
31.5 URL-адрес выглядит следующим образом:
http://Iocalhost/phpmysql3e/chapter31/index.php?expand=5#5
Сценарий интерпретирует приведенную строку как “развернуть элемент с иден-
тификатором postid, равным 10”. Символ # — всего лишь HTML-символ привязки,
который вызовет прокрутку страницы к только что развернутой части.
На рис. 31.6 URL-адрес имеет такой вид:
http://localhost/phpmysql3e/chapter31/index.php?expand=all
Щелчок на кнопке Развернуть привел к передаче параметра expand со значением
all.
Разворачивание и сворачивание
Теперь давайте посмотрим, как реализована обработка упомянутых выше дейст-
вий, исследовав код сценария index.php, который показан в листинге 31.2.
Листинг 31.2. index.php — сценарий создания представления статей на главной
странице приложения
<?php
include ('include_fns.php1);
session_start();
Глава 31. Разработка приложения поддержки Web-форумов
741
11 Проверить, создана ли переменная сеанса
if(!isset($_SESSION['expanded']))
{
$_SESSION['expanded'] = arrayO;
}
II Проверить, была ли нажата кнопка 'Развернуть'.
// Значением параметра expand может быть 'all',
// идентификатор postid или же значение может быть не установлено
if(isset($_GET['expand']))
{
if($_GET['expand'] == 'all')
expand_all($_SESSION['expanded']);
else
$_SESSION['expanded'][$_GET['expand']] = true;
)
// Проверить, была ли нажата кнопка 'Свернуть'.
II Значением параметра collapse может быть 'all',
// идентификатор postid или же значение может быть не установлено
if(isset($_GET['collapse']))
{
if($_GET['collapse']=='all')
$_SESSION['expanded'] = array();
else
unset($_SESSION['expanded'][$_GET['collapse']]);
)
do_html_header('Статьи в дискуссиях');
display_index_toolbar();
II Вывести древовидное представление бесед
display_tree($_SESSION['expanded']);
do_html_footer();
Для выполнения своей задачи этот сценарий использует три переменных:
Переменная сеанса expanded, отслеживающая развернутые цепочки. Эта пере-
менная может обновляться от представления к представлению, поэтому можно
иметь несколько развернутых цепочек. Переменная expanded представляет со-
бой ассоциативный массив, содержащий идентификаторы postid статей, от-
веты на которые будут отображаться в развернутом виде.
Параметр expand, указывающий сценарию, какие новые цепочки следует раз-
вернуть.
Параметр collapse, указывающий сценарию, какие цепочки следует свернуть.
В результате щелчка на символе плюса или минуса или на кнопке Развернуть или
Свернуть осуществляется повторный вызов сценария index. php с новыми значения-
ми параметров expand или collapse. Переменная expanded используется от страни-
цы к странице для отслеживания того, какие цепочки должны быть развернуты в ка-
ждом конкретном представлении.
742
Часть V. Реальные проекты на РНР и MySQL
Сценарий начинается с создания сеанса и добавления переменной expanded как
переменной сеанса, если это еще не было сделано.
Далее сценарий проверяет, был ли ему передан параметр expand или collapse, и
соответствующим образом изменяет массив expanded. Вот как выглядит код, опреде-
ляющий значение параметра expand:
if(isset($_GET['expand']))
{
if($_GET['expand'] == 'all')
expand_all($_SESSION['expanded' ] ) ;
else
$_SESSION['expanded'][$_GET['expand']] = true;
}
В результате щелчка на кнопке Развернуть вызывается функция expand_all (),
которая добавляет все цепочки, имеющие ответы, в массив expanded. (Это действие
рассматривается чуть ниже.)
Чтобы развернуть конкретную цепочку, ее идентификатор postid передается че-
рез параметр expand. Для отражения этого мы добавляем новую запись в массив
expanded.
Код функции expand_all () представлен в листинге 31.3.
Листинг 31.3. Функция expandall () из библиотеки discusslon_fns .php — эта функция
обрабатывает массив expanded для разворачивания всех цепочек в форуме
function expand_all(&$expanded)
{
//Помечает все цепочки с дочерними цепочками как отображаемые в развернутом виде
$conn = db_connect();
$query = 'select postid from header where children = 1';
$result = $conn->query($query);
$num = $result->num_rows;
for($i = 0; $i<$num; $i++)
{
$this_row = $result->fetch_row();
$ expanded[$ thi s_row[0]]= t rue;
}
}
Эта функция выполняет запрос к базе данных для выяснения того, какие цепочки
в форуме содержат ответы:
select postid from header where children = 1
После этого каждая из возращенных статей добавляется в массив expanded. Мы
выполняем этот запрос для того, чтобы сэкономить время в дальнейшем. Статьи
можно было бы просто добавить в список развернутых цепочек, однако впоследствии
мы могли бы столкнуться с напрасной тратой времени, когда пытались бы обработать
несуществующие ответы.
Сворачивание статей работает аналогично, но противоположным образом:
Главв 31. Разработка приложения поддержки WBb-форумов
743
if(isset($_GET['collapse']))
{
if($_GET['collapse']=='all')
$_SESSION['expanded'] = array();
else
unset($_SESSION['expanded'][$_GET['collapse']]);
}
Элементы из массива expanded можно удалять, отменяя их установку. Мы удаляем
сворачиваемую цепочку или же отменяем установку целого массива, если сворачива-
ется вся страница.
Все описанные действия относятся к этапу предварительной обработки, поэтому из-
вестно, какие статьи должны быть отображены, а какие — нет. Основной частью сцена-
рия является вызов функции display_tree ($_SESSION [' expanded' ]) ;, которая в дей-
ствительности генерирует дерево отображенных статей.
Отображение статей
Давайте рассмотрим код функции display_tree(), который представлен в лис-
тинге 31.4.
Листинг 31.4. Функция display_tree () из библиотеки output_fns .php — эта функция
создает корневой узел древовидной структуры
function display_tree($expanded, $row = 0, $start = 0)
{
// Выводит древовидное представление бесед
global $table_width;
echo "<table width='$table_width'>;
// Проверить, отображается полный список или подсписок
if($start>0)
$sublist = true;
else
$sublist = false;
// Создать древовидную структуру, представляющую беседу целиком
$tree = new treenode($start, '', '', '', 1, true, -1, $expanded, $sublist);
// Указать дереву на необходимость отобразить себя
$tree->display($row, $sublist);
echo '</table>';
2
Основная роль функции display_tree () заключается в создании корневого узла
древовидной структуры. Функция используется и для отображения всего дерева на
странице index. php, и для создания поддеревьев ответов на странице vi ew_post. php.
Как видите, функция принимает три параметра. Первый из них, $ expanded, — это
список идентификаторов postid статей, которые необходимо отображать в развер-
нутом виде. Второй, $row, представляет собой индикатор номера строки, который
будет использоваться для чередования цветов строк в списке.
744
Часть V. Реальные проекты на РНР и MySQL
Третий параметр, Sstart, сообщает функции, с какой статьи следует начинать ото-
бражение. Это идентификатор postid корневого узла дерева, которое должно быть
создано и отображено. Если нужно вывести весь список, как имеет место на главной
странице, значением этого параметра будет 0, то есть приложение будет отображать
все статьи, не имеющие родительских статей. В этом случае значение переменной
Ssublist устанавливается равным false и приложение выводит все дерево.
Если значение параметра больше 0, данный узел считается корневым узлом дере-
ва, которое требуется отобразить, значение Ssublist устанавливается равным true,
и приложение создает и отображает только часть дерева. (Собственно, это и будет
использоваться в сценарии view_post. php.)
Наиболее важной задачей, которую выполняет данная функция, является созда-
ние экземпляра класса treenode, представляющего корень дерева. В действительно-
сти он не является статьей, но действует в качестве родительской статьи для статей
первого уровня, которые фактически не имеют родительской статьи. После того как
дерево создано, для действительного отображения списка статей мы просто вызыва-
ем его функцию отображения.
Использование класса treenode
Код класса treenode представлен в листинге 31.5. (На этом этапе полезно еще раз
обратиться к главе 6, дабы освежить в памяти, как работают классы.)
Листинг 31.5. Класс treenode из файла treenode class.php — основная часть приложения
<?php
// В этом файле определены функции для загрузки, создания
// и отображения дерева
class treenode
{
// Каждый узел дерева имеет атрибуты, которые содержат все данные,
// необходимые для отправки всего, кроме тела сообщения
public $m_postid;
public $m_title;
public $m_poster;
public $m_posted;
public $m_children;
public $m_childlist;
public $m_depth;
public function___construct(Spostid, $title, $poster, $posted, $children,
$expand, Sdepth, Sexpanded, Ssublist)
{
// Конструктор устанавливает значения атрибутов, но, что еще
// важнее, он рекурсивно создает нижние части дерева
$this->m_postid = Spostid;
$this->m_title = Stitle;
$this->m_poster = Sposter;
$this->m_posted = Sposted;
$this->m_children =$children;
$this->m_childlist = arrayO;
$this->m_depth = Sdepth;
Глава 31. Разработка приложения поддержки Web-форумов
745
// Списки, расположенные ниже этого узла, представляют интерес, только
// если узел имеет дочерние списки, которые должны быть всегда развернуты
if(($sublist||$expand) && $children)
{
$conn = db_connect();
$query = "select * from header where parent = $postid order by posted";
$result = $conn->query($query);
for ($count=0; $row = @$result->fetch_assoc(); $count++)
{
if($sublist||$expanded[ $row['postid'] ] == true)
$expand = true;
else
$expand = false;
$this->m_childlist[$count]- new treenode($row['postid'],$row['title'] ,
$row['poster'],$row['posted'],
$row['children'], $expand,
$depth+l, $expanded, $sublist);
}
function display($row, Ssublist = false)
{
// Поскольку это объект, он сам отвечает за свое отображение.
// $row указывает, с какой строкой при отображении мы имеем дело.
// Таким образом, нам известно, каким цветом эта строка должна выводиться
// $sublist указывает, на какой странице мы находимся -
// на главной или на странице сообщения. Для страниц сообщений
// переменная $sublist равна true.
//В подсписках все сообщения развернуты и не содержат
// символов "+" и ”-".
// Если данный узел - пустой корневой узел, пропустить его вывод
i f($ thi s->m_depth>-1)
{
// Чередовать цвет вывода строк
echo '<tr><td bgcolor = ';
if ($row%2)
echo "'#cccccc'>";
else
echo "'tffffff'>";
// Вывести отступ в соответствие с глубиной вложения
for($i = 0; $i<$this->m_depth; $i++)
{
echo "<img src = 'images/spacer.gif' height = 22
width = 22 alt = '' valign = 'bottom' />";
}
// Вывести символ '+' или '-' или 'пробел'
if ( I$sublist && $this~>m_children && sizeof($this->m_childlist))
//Мы находимся на главной странице, имеем несколько дочерних узлов,
// и они развернуты
746
Часть V. Реальные проекты на РНР и MySQL
{
// Развернутое состояние - необходима кнопка сворачивания
echo "<а href = 1 index.php?collapse=".
$this->m_postid."#$this->m_postid1>
cimg src = 1 images/minus.gif1 valign = 'bottom'
height = 22 width = 22 alt = 'Свернуть цепочку'
border = 0 /></а>'',-
}
else if(!$sublist && $this->m_children)
{
// Свернутое состояние - необходима кнопка разворачивания
echo "<а href = 'index.php?expand=“.
$this->m_postid. ''#$this->m_postid' ximg src = ' images/plus. gif '
height = 22 width = 22 alt = 'Развернуть цепочку' border = Ox/a>";
}
else
(
// Дочерних элементов нет или же мы находимся в подсписке -
// не нужно никаких кнопок
echo ”<img src = ’images/spacer.gif’ height = 22 width = 22
alt = ’’ valign = 'bottom' />";
}
echo " <a name = $this->m_postid ><a href =
'view_post.php?postid=$this->m_postid’>$this->m_title -
$this->m_poster - ".reformat_date($this->m_posted).'</a>';
echo ' </tdx/tr>' ;
/I Увеличить значение счетчика строк для чередования цветов вывода
$row++;
}
// Вызвать метод display для каждого дочернего элемента этого узла.
// Обратите внимание, что узел будет иметь дочерние узлы в своем списке,
// только если он развернут
$num_children = sizeof($this->m_childlist);
for($i = 0; $i<$num_children; $i++)
{
$row = $this->m_childlist[$i]->display($row, $sublist);
}
return $row;
}
Этот класс инкапсулирует функциональность, которая управляет древовидным
представлением в рамках всего приложения.
Один экземпляр класса treenode содержит подробную информацию о единствен-
ной статье и связи со всеми ответными статьями этого класса. Это обусловливает ис-
пользование следующих атрибутов класса:
public $m_postid;
public $m_title;
public $m_poster;
Глава 31. Разработка приложения поддержки Web-форумов
747
public $m_posted;
public Sm_children;
public $m_childlist;
public $m_depth;
Обратите внимание, что treenode не содержит тела статьи. Нет никакой необхо-
димости загружать тело статьи до тех пор, пока пользователь не обратится к сцена-
рию view_post.php. Нужно постараться выполнить вывод древовидного представле-
ния сравнительно быстро, поскольку для отображения списка в форме дерева
требуется осуществлять множество манипуляций с данными, а при обновлении стра-
ницы или нажатии на какую-нибудь кнопку — заново выполнять все вычисления.
Имена этих переменных выбирались в соответствие со схемой именования,
обычно используемой в объектно-ориентированных приложениях — их имена начи-
наются с последовательности ш_, которая служит напоминанием о том, что они явля-
ются атрибутами класса.
Большинство этих переменных непосредственно соответствуют строкам из таб-
лицы header нашей базы данных.
Исключение составляют лишь переменные $m_childlist и $m_depth. Переменная
$m_childlist будет использоваться для хранения ответов на данную статью. Пере-
менная $m_depth будет хранить количество уровней дерева от корня до текущего
уровня — она будет применяться для отображения.
Функция конструктора устанавливает значения всех переменных. Вот как выгля-
дит соответствующий код:
public function __construct($postid, $title, $poster, Sposted, $children,
$expand, Sdepth, $expanded, $sublist)
{
// Конструктор устанавливает значения атрибутов, но, что еще
Il важнее, он рекурсивно создает нижние части дерева
$this->m_postid = Spostid;
$this->m_title = Stitle;
$this->m_poster = Sposter;
$this->m_posted = Sposted;
$this->m_children =$children;
$this->m_childlist = arrayO;
$this->m_depth = Sdepth;
При создании экземпляра treenode, который представляет корневой узел, в
display__tree () на главной странице фактически мы создаем фиктивный (dummy)
узел, не имеющий никаких связанных с ним статей. В этом случае мы передаем опре-
деленные начальные значения:
Stree = new treenode(Sstart, '1, true, -1, Sexpanded, Ssublist);
В результате создается корневой узел, значение идентификатора postid которого
равно 0. Этой особенностью можно воспользоваться для поиска всех статей первого
уровня, поскольку они имеют нулевую родительскую статью. Глубина устанавливается
равной -1, поскольку в действительности этот узел не является частью отображения.
Все статьи первого уровня будут иметь нулевую глубину и будут отображаться у левого
края экрана. Последующие уровни отображаются с отступом вправо.
748
Часть V. Реальные проекты на РНР и MySQL
Наиболее важный момент, который присутствует в конструкторе, — это создание
экземпляров дочерних узлов для данного узла. Этот процесс начинается с проверки
необходимости разворачивания дочерних узлов. Данное действие выполняется толь-
ко в том случае, если узел имеет какие-либо дочерние узлы, и мы должны их отобра-
зить:
if(($sublist||$expand) && $children)
{
$conn = db_connect();
Затем мы подключаемся к базе данных и получаем все дочерние статьи следующим
образом:
$query = "select * from header where parent = $postid order by posted";
$result = $conn->query($query);
Далее массив $m_childlist заполняется экземплярами класса treenode, которые
содержат ответы на статью, сохраненную в treenode:
for ($count=0; $row = @$result->fetch_assoc(); $count++)
{
if(Ssublist||$expanded[ $row['postid'] ] == true)
$expand = true;
else
$expand = false;
$this->m_childlist[$count]= new treenode($row['postid'],$row['title'],
$row['poster'],$row['posted'],
$row['children'], $expand,
$depth+l, $expanded, $sublist);
}
Последняя строка обеспечивает создание новых экземпляров treenode в соответ-
ствии с только что рассмотренным процессом, но уже для следующего уровня дерева.
Это относится к рекурсивной части функции. Родительский узел дерева вызывает
конструктор класса treenode, передавая собственный идентификатор postid в каче-
стве родительского узла и добавляя 1 к собственному значению глубины перед его
передачей.
Каждый экземпляр treenode будет по очереди создаваться, а также создавать соб-
ственные дочерние объекты до тех пор, пока не исчерпается список ответных статей
или не будут развернуты все требуемые уровни.
После того как все это выполнено, мы вызываем функцию отображения корнево-
го экземпляра treenode (внутри функции display_tree ()):
$tree->display($row, $sublist);
Функция display () начинается с проверки того, является ли данный узел фик-
тивным корневым узлом:
if($this->m_depth > -1)
Таким образом, фиктивный узел может быть исключен из отображения. Однако
нам не требуется полностью пропускать корневой узел. Он должен отображаться, но
при этом должен уведомлять свои дочерние узлы о том, что они должны отобразить
себя самостоятельно.
Глава 31. Разработка приложения поддержки Web-форумов
749
Затем функция начинает формировать таблицу, содержащую статьи. При этом ис-
пользуется операция деления по модулю (%), с помощью которой определяется тре-
буемый цвет фона данной строки (поскольку цвета фона чередуются):
// Чередовать цвет вывода строк
echo '<tr><td bgcolor="';
if ($row%2)
echo 1#cccccc">';
else
echo 1#ffffff">';
Далее с использованием атрибута $m_depth выясняется величина отступа, которая
необходима для текущего элемента. Как легко убедиться, взглянув на приведенные
рисунки, чем глубже уровень ответного сообщения, тем с большим отступом оно вы-
водится. Упомянутые действия реализуются следующим образом:
// Вывести отступ в соответствие с глубиной вложения
for($i = 0; $i < $this->m_depth; $i++)
{
echo "<img src='images/spacer.gif' height='22'
width='22' alt='' valign='bottom' />";
}
Следующая часть этой функции служит для определения того, нужно ли отобра-
жать кнопку плюса, минуса или вообще ничего:
/ / Вывести символ ' +' или ' - ' или ' пробел'
if ( !$sublist && $this->m_children && sizeof($this->m_childlist))
//Мы находимся на главной странице, имеем несколько дочерних узлов,
//и они развернуты
{
// Развернутое состояние — необходима кнопка сворачивания
echo "<а href=1 index.php?collapse=".
$this->m_postid."#$this->m_postid'>
<img src='images/minus.gif' valign='bottom'
height='22' width='22' alt='Свернуть цепочку' border='0' /></а>";
}
else if(!$sublist && $this->m_children)
{
// Свернутое состояние - необходима кнопка разворачивания
echo "<а href=1 index.php?expand=".
$this->m_postid."#$this->m_postid'xirng src='images/plus.gif'
height='22' width='22' alt='Развернуть цепочку' border='0'></a>”;
}
else
{
// Дочерних элементов нет или же мы находимся в подсписке -
// не нужно никаких кнопок
echo "<img src='images/spacer.gif' height='22' width='22'
alt='' valign='bottom'>";
}
После этого отображаются фактические сведения об этом узле:
750
Часть V. Реальные проекты на РНР и MySQL
echo "<a name=$this->m_postidxa href=
'view_post.php?postid=$this->m_postid'>$this->m_title -
$this->m_poster - ".reformat_date($this->m_posted).'</a>';
echo ' </tdx/tr>' ;
Цвета следующей строки должен быть изменен:
// Увеличить значение счетчика строк для чередования цветов вывода
$row++;
Затем идет фрагмент кода, который будет выполняться всеми экземплярами
treenode, в том числе корневым:
// Вызвать метод display для каждого дочернего элемента этого узла.
// Обратите внимание, что узел будет иметь дочерние узлы в своем списке,
// только если он развернут
$num_children - sizeof($this->m_childlist);
for($i =0; $i < $num_children; $i++)
{
$row = $this->m_childlist[$i]->display($row, $sublist);
}
return $row;
Это снова вызов рекурсивной функции, который выполняется для каждого дочер-
него узла данного узла, чтобы они смогли отобразить себя. Им передается текущий
цвет строки, а они передают его обратно по завершении работы с ним, что позволяет
легко отслеживать чередование цветов.
На этом знакомство с классом treenode завершено. Вы смогли убедиться, что его
код достаточно сложен. Возможно, имеет смысл сначала поэкспериментировать с
приложением, а затем, разобравшись с его работой, попытаться другим взглядом по-
смотреть на этот код.
Просмотр отдельных статей
Вызов функции display_tree () приводит к созданию ссылок на набор статей. Ес-
ли щелкнуть на одной из таких ссылок, мы войдем в сценарий view_post.php со зна-
чением параметра postid, которое будет соответствовать просматриваемой статье.
Пример вывода, сгенерированного этим сценарием, показан на рис. 31.7.
Сценарий view_post.php отображает тело сообщения, а также ответы на данное
сообщение. Как видите, ответы снова отображаются в форме дерева, но на этот раз
полностью развернутого без каких-либо кнопок плюсов или минусов. Это — результат
действия переключателя $sublist.
Давайте рассмотрим код сценария view_post .php, который представлен в листинге
31.6.
Листинг 31.6. view post .php — этот сценарий отображает тело отдельного сообщения
<?php
// Включить библиотеки функций
include ('include_fns.php1);
$postid = $_GET['postid'];
// Получить детальную информацию о статье
Глава 31. Разработка приложения поддержки Web-форумов
751
$post = get._post ($postid) ;
do._htiril_header ($post [1 title’ ]) ;
// Отобразить статью
display ...post ($post) ;
11 Если co статьей связаны ответы, вывести их древовидное представление
if($post['children'])
{
echo '<br /><br />';
display_.replies_.line () ;
display_.trее ($_SESSION[ ' expanded' ] , 0, $postid) ;
}
do_html_footer() ;
Для выполнения стоящей перед этим сценарием задачи используются три основ-
ных функции: get_post(), display_post() и display_tree().
SW: До] *1
Файл Гривка Вид Переход Закладки Инструменты Окно Сграека Debug
® 1 J. http://localhosVphpmysql3» *j: ^Г10И1ж| jF"
Назад обновить - —J
How do I make a variable carry over from page to page?
Guest
Replies to this тессэдо
Re persistence? - Laura - 14 19 06/27/2005
Re: persistence? - Guest-14:20 06/27/2005
\ Re persistence? - Luke - 14 21 06/27/2005
Рис. 31.7. Сейчас можно видеть тело данной статьи
Функция get_post() извлекает информацию из базы данных. Код этой функции
можно найти в листинге 31.7.
Листинг 31.7. Функция get_post () из библиотеки discussion !ns.php — эта функция
извлекает сообщение из базы данных
function get_post($postid)
{
// Извлекает из базы данных одну статью и возвращает ее в виде массива
if(!$postid) return false;
$conn = db_connect();
752
Часть V. Реальные проекты на РНР и MySQL
II Получить всю информацию о заголовках из 'header'
$query = "select * from header where postid = $postid”;
$result = $conn->query($query);
if($result->num_rows != 1)
return false;
$post = $result->fetch_assoc() ;
// Получить сообщение из тела и добавить его к предыдущему результату
$query = "select * from body where postid = $postid”;
$result2 = $conn->query($query);
if($result2->num_rows > 0)
{
$body = $result2->fetch_assoc();
if(Sbody)
{
$post['message'] = $body['message'];
}
}
return $post;
Для данного идентификатора postid эта функция выполняет два запроса, необхо-
димые для получения заголовка и тела сообщения для определенной статьи, и поме-
щает их в возвращаемый ею массив.
Затем результат выполнения этой функции передается в функцию display_post ()
из библиотеки output_fns . php. Эта функция всего лишь выводит массив, выполняя
незначительный объем форматирования HTML-текста, поэтому подробно мы на ней
останавливаться не будем.
И, наконец, сценарий view_post .php проверяет, имеются ли какие-то ответы на дан-
ную статью, и вызывает функцию display_tree () для отображения их в формате под-
списка— то есть полностью развернутыми, без каких-либо кнопок плюсов или минусов.
Добавление новых статей
Теперь посмотрим, как новая статья добавляется в форум. Пользователь может
сделать это двумя способами: во-первых, щелкнув на кнопке Новая на странице
index. php, и, во-вторых, щелкнув на кнопке Ответить на странице view__post. php.
Оба эти действия активизируют один и тот же сценарий new_post .php, но пере-
дают ему разные параметры. На рис. 31.8 показан вывод, сгенерированный сценари-
ем new_post .php после щелчка на кнопке Ответить.
Первым делом, взгляните на URL-адрес:
http://localhost/phpmysql3e/chapter3l/new_post.php?parent=18
Параметр, переданный как parent, будет идентификатором postid родитель-
ского сообщения нового сообщения. Если вместо кнопки Ответить щелкнуть на
кнопке Новая, в URL-адресе будет присутствовать parent=0.
Во-вторых, как видите, в случае создания ответа текст исходного сообщения
вставляется и помечается символом >, как принято в большинстве программ чтения
электронной почты и новостей.
Глава 31. Разработка приложения поддержки Web-форумов
753
Рис. 31.8. Ответы содержат автоматически встав-
ленный и помеченный текст оригинальной статьи
В-третьих, заголовок этого сообщения по умолчанию повторяет заголовок исход-
ного сообщения, но с префиксом Re:.
А теперь рассмотрим код, который генерирует этот вывод — он показан в листин-
ге 31.8.
Листинг 31.8. new__post .php — этот сценарий дает пользователю возможность ввести
новую статью или ответить на существующую статью
<?php
include ('include_fns.php');
Stitle = $_POST['title'];
$poster = $_POST['poster'];
Smessage = $_POST['message'];
if(isset($_GET['parent']))
Sparent = $_GET['parent'];
else
Sparent = $_POST['parent'];
if(!Sarea)
$area = 1;
if(!Serror)
(
if(!Sparent)
[
Sparent = 0;
if(!Stitle)
Stitle = 'Новая статья';
1
else
{
754
Часть V. Реальные проекты на РНР и MySQL
// Получить название статьи
$title = get_post_title(Sparent);
/7 Добавить Re:
if(strstr(Stitle, 'Re: ') == false )
Stitle = 'Re: '.Stitle;
// Проверить, помещается ли заголовок в базу данных
$title = substr($title, 0, 20);
/7 Добавить статью, на которую дается ответ, в форме цитируемого сообщения
Smessage = add_quoting(get_post_message(Sparent));
J
}
do_html_header(Stitle) ;
display_new_post_form(Sparent, $area, Stitle, Smessage, Sposter);
if(Serror)
{
echo 'Ваше сообщение не сохранено.
Проверьте, заполнены ли все поля, и повторите попытку.';
)
do_html_footer();
После выполнения ряда начальных настроек этот сценарий проверяет, является
ли поле родительской статьи (parent) нулевым или же каким-то другим. Если значе-
ние его нулевое, значит, это новая тема, поэтому требуется предпринять некоторые
дополнительные действия.
Если это ответ (Sparent представляет собой идентификатор postid существую-
щей статьи), сценарий устанавливает заголовок и текст исходного сообщения:
// Получить название статьи
Stitle = get_post_title(Sparent);
// Добавить Re:
if(strstr(Stitle, 'Re: ') == false )
Stitle = 'Re: '.Stitle;
// Проверить, помещается ли заголовок в базу данных
Stitle = substr(Stitle, 0, 20);
// Добавить статью, на которую дается ответ, в форме цитируемого сообщения
Smessage = add_quoting(get_post_message(Sparent));
В этом фрагменте кода используются функции get_post_title (), get_post_message ()
и add_quoting() из библиотеки discussion_fns.php. Их код можно найти в
листингах 31.9, 31.10 и 31.11, соответственно.
Листинг 31.9. Функция get_post_title () из библиотеки discussion_f ns .php —
эта функция извлекает из базы данных заголовок сообщения
function get_post_title($postid)
{
// Извлекает из базы данных название одной статьи
if(!Spostid) return '';
Глава 31. Разработка приложения поддержки Web-форумов
755
Sconn = db_connect () ;
// Получить всю информацию о заголовке из 'header'
Squery = "select title from header where postid = Spostid";
Sresult = $conn->query(Squery);
if($result->num_rows != 1)
return '';
$this_row = $result->fetch_array():
return $this_row[0];
Листинг 31.10. Функция get_post_message() из библиотеки discussionfns.php —
эта функция извлекает из базы данных тело сообщения
function get_post_message(Spostid)
{
// Извлекает из базы данных тело одной статьи
if(!Spostid) return '';
Sconn = db_connect();
Squery = "select message from body where postid = Spostid";
Sresult = Sconn->query(Squery)
if($result->num_rows > 0)
{
$this_row = $result->fetch_array();
return $this_row[0];
}
}
Первые две функции получают, соответственно, заголовок и тело статьи из базы
данных.
Листинг 31.11. Функция addquoting() из библиотеки discussionfns.php —
эта функция отображает текст сообщения с отступами и символами >
function add_quoting($string, Spattern = ' > ')
{
// Помечает текст как цитируемый с использованием шаблона ’> '
return Spattern.str_replace("\n", "\n$pattern", Sstring);
)
Функция add_quoting () изменяет форматирование строки, начиная каждую строку
исходного текста определенным символом, которым по умолчанию является >.
После того как пользователь вводит свой ответ и щелкает на кнопке Отправить,
активизируется сценарий store_new_post.php. Пример вывода, сгенерированного
этим сценарием, показан на рис. 31.9.
В этом примере новая статья располагается в строке с меткой Re: using gd? —
Youryart — 14:27 06/27/2005. В остальном же эта страница выглядит подобно
обычной странице index.php.
Рассмотрим код сценария store_new_post.php более подробно. Его можно найти
в листинге 31.12.
756
Часть V. Реальные проекты на РНР и MySQL
Рис. 31.9. Теперь в древовидном представлении
можно видеть новую статью
Листинг 31.12. store new post .php — этот сценарий помещает в базу данных новую статью
<?php
include (1include_fns.php');
if($id = store_new_post($_POST))
{
include ('index.php');
}
else
{
$error = true;
include ('new_post.php');
}
Как видите, этот сценарий очень короткий. Его основная задача состоит в вызове
функции store_new_post (). Эта страница не имеет собственного видимого со-
держимого. Если сохранение выполнилось успешно, мы будем наблюдать страни-
цу index.php. В противном случае снова отображается страница new_post.php, что-
бы пользователь смог повторить попытку, которая ранее завершилась неудачей.
Код функции store_new_post () показан в листинге 31.13.
Листинг 31.13. Функция store_new_post () из библиотеки discussion—fns .php —
эта функция проверяет и сохраняет новую статью в базе данных
function store_new_post($post)
{
// Проверяет допустимость и затем сохраняет новую статью в базе данных
$conn = db_connect();
// Проверить, что ни одно поле не оставлено пустым
if(!filled_out($post))
{
Глава 31. Разработка приложения поддержки Web-форумов
757
return false;
}
$post = clean_all(Spost);
// Проверить, существует ли родительская статья
if($post['parent']! =0)
{
Squery = "select postid from header where postid = '".Spost['parent;
Sresult = $conn->query(Squery);
if($result->num_rows != 1)
{
return false;
)
)
// Проверить, не появится ли дубликат
Squery = "select header.postid from header, body where
header.postid = body.postid and
header.parent = ".Spost['parent']." and
header.poster = '".Spost['poster’ and
header.title = '".Spost['title’].”' and
header.area = ".Spost['area']." and
body.message = '".Spost('message';
Sresult = $conn->query(Squery);
if (!Sresult)
{
return false;
)
if(Sresult->num_rows > 0)
{
$this_row = $result->fetch_array();
$id = $this_row[0];
)
Squery = “insert into header values
('".Spost['parent
'”.Spost['poster
'".Spost['title']."’,
0,
’".Spost[’area
now(),
NULL
)";
Sresult = $conn->query(Squery);
if (!Sresult)
{
return false;
}
// Обратите внимание, что теперь родительская статья имеет дочернюю статью
Squery = 'update header set children = 1 where postid = '.Spost['parent'];
Sresult = $conn->query(Squery);
if (!Sresult)
{
return false;
}
758
Часть V. Реальные проекты на РНР и MySQL
// Выяснить идентификатор данной статьи. Обратите внимание, что вполне
// может существовать несколько почти идентичных статей, которые
// различаются только идентификаторами и, возможно, временем отправки.
Squery = "select header.postid from header left join body
on header.postid = body.postid
where parent = $post['parent1]."'
and poster = Spost['poster']."'
and title = $post['title1].”'
and body.postid is NULL";
$result = $conn->query(Squery);
if (!Sresult)
{
return false;
}
if($result->num_rows > 0)
{
$this_row = $result->fetch_array();
$id = $this_row[0];
}
if($id)
{
Squery = "insert into body values ($id, '".Spost['message']."')";
Sresult = $conn->query(Squery);
if (!Sresult)
(
return false;
}
return $id;
)
Код этой функции нельзя назвать коротким, однако он не особенно сложный.
Столь большой объем кода обусловлен лишь тем, что вставка статьи означает вставку
записей в таблицы заголовков (header) и тела сообщений (body), а также обновление
строки родительской статьи (parent) в таблице header для отражения того, что те-
перь родительская статья имеет дочернюю статью.
Что ж, на этом мы можем благополучно завершить рассмотрение кода приложе-
ния поддержки Web-форумов.
Расширение проекта
Подобно ранее рассмотренным учебным проектам, в этот проект можно добавить
множество расширений:
В дополнение к кнопкам представления можно добавить навигационные кноп-
ки, чтобы от одного сообщения можно было переходить к следующему сооб-
щению, предыдущему сообщению, к следующему сообщению в цепочке или
предыдущему сообщению в цепочке.
Глава 31. Разработка приложения поддержки Web-форумов
759
Можно добавить интерфейс администрирования для создания новых форумов
и удаления устаревших статей.
Можно реализовать аутентификацию пользователей, чтобы отправку статей
могли совершать только зарегистрированные пользователи.
Можно добавить какой-нибудь механизм цензуры, который бы позволил изба-
виться от нежелательной (и оскорбительной) лексики.
Идеи по расширению приложения можно почерпнуть из великого множества су-
ществующих систем.
Использование существующих систем
Одной заслуживающей внимания системой является Phorum — проект по органи-
зации Web-форумов с открытым исходным кодом. Его средства навигации по страни-
цам и семантика отличаются от описанных в данной главе, а его структуру сравни-
тельно легко приспособить под конкретный сайт. Примечательным свойством
системы Phorum является то, что реальный пользователь может настроить ее на ото-
бражение статей в виде цепочек или в простом планарном виде. Дополнительную
информацию об этой системе можно получить на сайте по адресу:
http://www.phorum.org/
Что дальше
В следующей главе мы рассмотрим использование PDF-формата для создания при-
влекательных, единообразно печатаемых и частично защищенных документов. Такая
функциональность очень полезна для широкого спектра приложений, основанных на
службах, таких как приложения с возможностью онлайновой генерации контрактов.
760
Часть V. Реальные проекты на РНР и MySQL
32
Генерация
персонифицированных
документов в PDF-формате
На сайтах, ориентированных на предоставление услуг, очень часто требуется
доставлять персонифицированные документы, генерируемые в ответ на вводи-
мую посетителями информацию. Такая функциональность может служить для пре-
доставления автоматически заполняемых форм или для генерации таких персони-
фицированных документов, как контракты, письма и сертификаты.
В примере, которому будет посвящена данная глава, пользователю предоставляет-
ся Web-страница онлайнового экзамена на профессиональную пригодность, и на ос-
нове успешности сдачи экзамена генерируется соответствующий сертификат.
В главе будут рассмотрены следующие темы:
Как использовать обработку строк в РНР, чтобы объединить шаблон с пользо-
вательскими данными с целью создания документа в расширенном текстовом
формате (Rich Text Format — RTF).
Как воспользоваться аналогичным подходом для создания документа в форма-
те переносимых документов (Portable Document Format — PDF).
Как использовать PHP-функции из библиотеки PDFlib для генерации PDF-
документов.
Задача
В рамках этого проекта мы предоставим посетителям сайта возможность сдать эк-
замен, который сводится к ответам на ряд предлагаемых вопросов. В случае правиль-
ных ответов на достаточное количество вопросов для посетителя будет генериро-
ваться сертификат, подтверждающий успешную сдачу экзамена.
Для того чтобы компьютер смог адекватно оценить ответы, каждый вопрос будет
связан с некоторым набором ответов. Из всех потенциальных ответов на каждый из
вопросов только один ответ является правильным.
Если пользователь наберет достаточное количество баллов за ответы на вопросы,
ему будет выдан сертификат. В идеальном случае формат файла сертификата должен
соответствовать следующим требованиям:
1. Быть простым по дизайну.
2. Быть пригодным для помещения в него набора различных элементов, таких
как растровые и векторные изображения.
3. Обеспечивать высокое качество при выводе на печать.
4. Требовать загрузки файла относительно небольших размеров.
5. Генерироваться практически мгновенно.
6. Требовать небольших затрат по созданию.
1. Работать под управлением множества типовых операционных систем.
8. С трудом поддаваться подделке или модификации.
9. Не требовать никакого специального программного обеспечения для просмот-
ра или печати.
10. Поддерживать единообразное отображение и печать для всех получателей.
Подобно множеству решений, которые приходится принимать время от времени,
для выбора варианта, удовлетворяющего максимальному количеству из десяти пере-
численных требований, наверняка, придется пойти на какой-нибудь компромисс.
Оценка форматов документов
Наиболее важное решение связано с выбором формата, в котором должен предос-
тавляться сертификат. В число возможных форматов входит бумажная копия, ASCII-
формат, HTML-формат, формат Microsoft Word или какого-то другого текстового
процессора, RTF-формат, PostScript-формат и PDF-формат. Принимая во внимание
перечисленные ранее требования, имеет смысл рассмотреть и сравнить некоторые
из доступных возможностей.
Бумажная копия
Предоставление сертификата на бумаге обладает рядом очевидных преиму-
ществ. В этом случае сохраняется полный контроль над всем процессом. Перед от-
правкой адресату мы видим, как в точности выглядит каждый экземпляр сертифи-
ката. Мы не должны беспокоиться о каком-то программном обеспечении или
пропускной способности сети, а отпечатанный сертификат при желании несложно
защитить от подделки.
Сертификат в таком виде соответствовал бы всем перечисленным требованиям, за
исключением пятого и шестого. Нет возможности быстро его создать и доставить
тому, кому надобно. Доставка по почте может занять несколько дней или даже недель,
в зависимости от местонахождения адресата.
Затраты на печать и доставку по почте каждого сертификата на бумаге составила
бы от нескольких центов до нескольких долларов, а в случае доставки курьером —
скорее всего, намного больше. Автоматическая доставка по электронной почте, по
идее, обходится гораздо дешевле.
762
Часть V. Реальные проекты на РНР и MySQL
ASCII-формат
Доставка документов в формате ASCII или обычного текста обладает некоторыми
преимуществами. Совместимость не будет составлять никаких проблем. Требуемая
пропускная способность может быть небольшой, поэтому стоимость доставки доста-
точно низка. Простота конечного результата обусловит простоту разработки и очень
большую скорость генерирования документа сценарием.
Однако если посетителям предоставляется ASCII-файл, внешний вид сертифика-
та, по сути, неконтролируемый. Мы не можем управлять шрифтами или разрывами
страниц. Можно только помещать текст и в незначительной степени управлять фор-
матированием. Кроме того, отсутствует возможность контролировать дублирование
или модификацию документа получателем. При использовании этого метода получа-
телю проще всего подделать свой сертификат.
HTML-формат
Естественным форматом при доставке документа через Web является HTML-
формат. Язык гипертекстовой разметки (Hypertext Markup Language — HTML) как
раз для этого специально и предназначен. Как, несомненно, читатели уже знают, он
включает в себя управление форматированием, синтаксис для вставки таких объек-
тов, как изображения, и совместим (с некоторыми различиями) с множеством опера-
ционных систем и программ. Этот формат очень прост, поэтому разработка докумен-
та в нем будет простой, а генерация и доставка его сценарием — быстрой.
К недостаткам использования HTML-формата для нашего приложения относится
ограниченная поддержка таких связанных с печатью атрибутов форматирования, как
разрывы страниц; недостаточное единообразие вывода на различных платформах и в
различных программах; качество печати, которое варьируется в довольно широких
пределах. Кроме того, хотя HTML-документ может содержать внешние элементы лю-
бого типа, отображение или использование этих элементов браузером для нестан-
дартных типов не гарантируется.
Форматы текстовых процессоров
Для проектов, рассчитанных на работу в локальных сетях, предоставление доку-
ментов в формате текстового процессора имеет определенный смысл. Однако при
доставке через Internet применение собственного формата текстового процессора
может привести к отсеву некоторых посетителей. Тем не менее, учитывая его преоб-
ладание на рынке, использование формата Microsoft Word вполне оправдано. Боль-
шинство пользователей будут иметь доступ либо к Word, либо к текстовому процес-
сору, который может читать файлы в этом формате, например, OpenOffice Writer.
Пользователи Windows, не имеющие Word, могут бесплатную загрузить программу
просмотра документов Word, доступную по следующему адресу:
http://www.microsoft.сот/office/000/viewers.asp
Генерация документа в формате Microsoft Word обладает рядом преимуществ. При
наличии копии программы Word разработка документа не представляет трудности.
Можно в большой степени управлять внешним видом документов при печати, а также
иметь множество возможностей в отношении содержимого. Кроме того, изменение
Глава 32. Генерация персонифицированных документов в PDF-формате
763
документа получателем можно сделать сравнительно трудным, указав Word на необ-
ходимость запроса пароля на модификацию.
К сожалению, файлы в формате Word могут быть большими, особенно если они
содержат изображения или другие сложные элементы. Кроме того, отсутствует про-
стой способ динамической их генерации из РНР. Формат документирован, однако
является бинарным, а документация по формату поставляется в соответствии с усло-
виями лицензионного соглашения. Имеется возможность генерировать документы
Word с помощью COM-объекта, однако это весьма непросто.
В последнее время появилась альтернативная возможность применять вместо
Word текстовый процессор OpenOffice Writer, с которым связано два преимущества:
это свободное программное обеспечение и в нем может использоваться формат фай-
лов XML. Word 2003 сейчас также поддерживает формат файлов XML. Определение
типа документа (Document Type Definition — DTD) для Word и других продуктов Of-
fice можно загрузить из Web-сайта компании Microsoft. Найдите на сайте ссылку на-
подобие “Office 2003 XML Reference Schemas”. В принципе, данная альтернатива хо-
роша, однако реализуется весьма непросто.
Расширенный текстовый формат
Расширенный текстовый формат (Rich Text Format), или RTF-формат, предлагает
большинство возможностей, доступных в формате Word, но RTF-файлы гораздо
проще генерировать. Мы по-прежнему располагаем множеством возможностей в от-
ношении макета и форматирования печатной страницы. В документ по-прежнему
можно помещать такие элементы, как векторные или растровые изображения.
Опять-таки, по-прежнему можно не сомневаться, что при просмотре или печати до-
кумента пользователь получит результаты, аналогичные запланированным.
RTF — это текстовый формат Microsoft Word. Он служит форматом обмена во вре-
мя передачи документов между различными программами. В определенном смысле
он аналогичен HTML-формату. Для передачи информации о форматировании в нем
применяется синтаксис и ключевые слова, а не бинарные данные, поэтом}' он срав-
нительно читабелен для человека.
Этот формат хорошо документирован. Его спецификация доступна для бесплат-
ной загрузки, и ее можно найти по следующему адресу:
http://msdn.microsoft.сот/library/default.asp?url=/library/
en-us/dnrtfspec/html/rtfspec.asp
Простейший способ генерации RTF-документа состоит в выборе команды Save As
RTF (Сохранить как RTF) в используемом текстовом процессоре. Поскольку RTF-
файлы содержат только текст, их можно генерировать непосредственно, а сущест-
вующие документы легко изменять.
Поскольку формат документирован, причем документация доступна бесплатно,
документы в этом формате могут читаться большим количеством программ, нежели
документы в бинарном формате Word. Однако следует иметь в виду, что пользовате-
ли, открывающие сложный RTF-файл в более ранних версиях Word или в других тек-
стовых процессорах, часто получают несколько иные конечные результаты. Каждая
новая версия Word добавляет в RTF-формат новые ключевые слова. Поэтом}’, как
764
Часть V. Реальные проекты на РНР и MySQL
правило, более ранние версии будут игнорировать элементы управления, которые
они не распознают или которые было решено не реализовывать.
Что касается ранее приведенного перечня требований, то сертификат в RTF-
формате можно было бы легко разработать, используя Word или какой-то другой тек-
стовый процессор. Этот сертификат может содержать множество различных элемен-
тов типа векторных или растровых изображений. Этот формат позволяет получить
высококачественную печатную копию. Документ может быть сгенерирован легко и
быстро. Документ может доставляться электронными средствами с относительно
низкими затратами.
Формат будет работать с разнообразными приложениями и операционными сис-
темами, хотя и приводя к слегка разным результатам. С другой стороны, RTF-
документ свободно и без труда может быть изменен любым пользователем, что неже-
лательно для сертификатов и других типов документов. Размеры сложных докумен-
тов могут оказаться достаточно большими.
RTF-формат вполне подходит для многих приложений доставки документов, по-
этому в рассматриваемом примере мы будем его использовать в качестве одного из
вариантов.
PostScript-формат
PostScript, разработанный компанией Adobe, представляет собой язык описания
страниц. Это сложный язык программирования, обладающий большими возможно-
стями, который предназначен для представления документов в форме, не зависящей
от устройства. Другими словами, он обеспечивает описание, которое будет приво-
дить к одинаковым результатам на различных устройствах, таких как принтеры и мо-
ниторы. Этот формат очень хорошо документирован. Ему’ посвящено несколько объ-
емных книг, а также бесчисленное множество Web-сайтов.
PostScript-документ может содержать очень точное форматирование, текст, изо-
бражения, внедренные шрифты и другие элементы. PostScript-документ можно легко
генерировать из приложения, печатая его на драйвер принтера PostScript. При жела-
нии можно было бы даже научиться программировать на нем непосредственно.
PostScript-документы в достаточной степени пригодны для переноса из одной сис-
темы в другую. Они будут давать единообразные, высококачественные отпечатки на
различных устройствах и под управлением различных операционных систем.
Использование PostScript-формата для распространения документов имеет не-
сколько значительных недостатков:
Файлы могут иметь очень большие размеры.
Чтобы использовать PostScript-файлы, многим пользователям требуется загру-
жать дополнительное программное обеспечение.
Большинство пользователей Unix смогут работать с PostScript-файлами, однако
пользователям Windows, как правило, придется загрузить программу' просмотра, по-
добную GSview, в которой применяется интерпретатор Ghostscript PostScript. Суще-
ствуют варианты этой программы для множества платформ. Хотя она доступна бес-
платно, мы вовсе не намерены вынуждать пользователя выгружать дополнительное
программное обеспечение.
Глава 32. Генерация персонифицированных документов в PDF-формате
765
Более подробная информация о Ghostscript доступна на сайте по адресу:
http://www.ghostscript.сот/
а загрузить программу просмотра можно по адресу:
http://www.es.wise.edu/~ghost/
Применительно к создаваемому приложению формат PostScript очень хорошо
подходит для создания единообразного высококачественного вывода, однако он не
обеспечивает выполнение большинства других требований.
Переносимый формат документов
К счастью, существует формат, поддерживающий большинство возможностей
PostScript, но обладающий существенными преимуществами. Формат переносимых
документов (Portable Document Format — PDF), также разработанный компанией
Adobe, является средством распространения документов, которые ведут себя одина-
ково на различных платформах и обеспечивают предсказуемый высококачественный
вывод как на экране, так и на бумаге.
В документации, выпускаемой компанией Adobe, PDF описывается следующим
образом: “Это открытый стандарт де-факто для повсеместного распространения
электронных документов. PDF компании Adobe — универсальный формат файлов,
который сохраняет все шрифты, цвета форматирования и графические изображения
любого исходного документа независимо от приложения и платформы, использован-
ных для его создания. PDF-файлы компактны и могут использоваться совместно, про-
сматриваться, управляться и печататься именно так, как требуется, причем любым
пользователем, располагающим бесплатной программой Adobe Acrobat Reader”.
Документация по PDF доступна по адресу:
http://partners.adobe.com/asn/tech/pdf/specifications.jsp
а также на множестве других Web-сайтов и в официальной книге.
Учитывая удовлетворение выдвинутых нами требований, PDF выглядит весьма и
весьма многообещающе. PDF-документы обеспечивают единообразный, высококаче-
ственный вывод и могут содержать такие элементы, как растровые и векторные изо-
бражения. В них может использоваться сжатие с целью получения файла небольшого
размера. Они могут недорого доставляться электронными средствами, и пригодны
для использования в средах основных операционных систем. Эти документы могут
содержать элементы управления безопасностью.
Недостаток PDF-формата состоит в том, что большинство программ для создания
PDF-документов являются коммерческими.
Для просмотра PDF-файлов требуется программа чтения, но Adobe распространя-
ет бесплатные версии Acrobat Reader для Windows-, UNIX- и Macintosh-систем. Мно-
гие посетители сайта, наверняка, уже знакомы с расширением . pdf и, скорее всего,
будут иметь установленную программу чтения.
PDF-файлы служат хорошим средством распространения привлекательных, при-
годных для качественной печати документов, особенно когда нежелательно, чтобы
получатели могли их легко модифицировать. Мы рассмотрим два различных способа
генерации сертификата в PDF-формате.
766
Часть V. Реальные проекты на РНР и MySQL
Компоненты решения
Чтобы построить действительно работоспособную систему, потребуется иметь
возможность проэкзаменовать знания пользователей и (если они выдержали тест)
сгенерировать сертификат, подтверждающий их квалификацию. Мы поэксперимен-
тируем с генерацией такого сертификата тремя различными способами: двумя с ис-
пользованием PDF-формата и еще одним — с использованием RTF-формата.
А сейчас мы более подробно рассмотрим требования, предъявляемые к каждому
из этих компонентов.
Система вопросов и ответов
Создание гибкой системы онлайновых экзаменов, которая позволила бы приме-
нять множество различных типов вопросов и различных типов носителей для допол-
нительной информации, обеспечивала бы полезную реакцию на неправильные отве-
ты и продуманный сбор статистической информации и позволяла бы создавать
отчеты — задача сложная уже сама по себе.
В этой главе основное внимание уделено задаче создания персонифицированных
документов, предназначенных для доставки через Web. Поэтому мы решили разрабо-
тать лишь очень простую экзаменационную систему.
Эта экзаменационная система не требует использования никакого специализиро-
ванного программного обеспечения. Для задания вопросов в ней используется
HTML-форма, а для обработки ответов — PHP-сценарий. Мы уже делали что-то по-
добное, начиная с первой главы.
Программное обеспечение для генерации документов
Для генерации RTF- или PDF-документов из шаблонов никакого дополнительного
программного обеспечения устанавливать на Web-сервере не потребуется, однако
такое программное обеспечение будет необходимо для создания шаблонов. Чтобы
использовать PHP-функции создания PDF-документов, поддержку PDF придется ском-
пилировать в PHP-систему. (Подробнее этот вопрос будет рассматриваться несколько
позже.)
Программное обеспечение для создания
шаблона RTF-документов
Для генерации RTF-файлов можно использовать любой текстовый процессор по
своему выбору. Для создания шаблона сертификата мы применяли Microsoft Word.
Этот шаблон можно найти на сопровождающем книгу компакт-диске в каталоге для
этой главы.
Если предпочтение отдано другому текстовому процессору, все же имеет смысл
протестировать готовый шаблон в программе Word, поскольку именно эту программу
будет использовать большинство посетителей вашего сайта.
Глава 32. Генерация персонифицированных документов в PDF-формате
767
Программное обеспечение для создания
шаблона PDF-документов
Создание PDF-документов несколько сложнее. Простейший путь состоит в том,
чтобы приобрести программу Adobe Acrobat. Она позволит создавать качественные
PDF-документы из различных приложений. Именно эта программа применялась для
создания файла шаблона для данного проекта.
Для создания документа использовался Microsoft Word. Одним из инструментов,
входящих в состав пакета Acrobat, является Adobe Distiller. В среде Distiller необхо-
димо выбрать несколько параметров, не устанавливаемых по умолчанию. Файл дол-
жен быть сохранен в формате ASCII, а сжатие должно быть отключено. После того
как эти параметры установлены, создание PDF-документа столь же просто, как и вы-
вод его на печать.
Более подробную информацию о программе Adobe Acrobat можно найти на сайте
по адресу:
http://www.adobe.com/products/acrobat/
Приобрести ее можно либо через Internet, либо в ближайшем магазине про-
граммного обеспечения.
Еще одну возможность создания PDF-документов предоставляет программа пре-
образования ps2pdf, которая, как должно быть понятно из ее названия, преобразует
PostScript-файлы в PDF-файлы. Ее преимущество состоит в том, что она распростра-
няется бесплатно. Однако она не всегда обеспечивает хорошие результаты для доку-
ментов, которые содержат изображения или нестандартные шрифты. Программа
преобразования ps2pdf поставляется с упоминавшимся ранее пакетом Ghostscript.
Понятно, что если решено создавать PDF-файл таким способом, вначале придется
создать PostScript-файл. Как правило, для этого UNIX-пользователи будут применять
утилиту a2ps или dvips.
При работе в среде Windows PostScript-файлы можно создавать, не прибегая к ис-
пользованию программы Adobe Distiller, хотя и посредством несколько более слож-
ного процесса. Придется установить драйвер принтера PostScript. Например, можно
использовать драйвер Apple LaserWriter IINT. Если драйвер PostScript не установлен,
его можно загрузить из сайта компании Adobe:
http://www.adobe.com/support/downloads/product.jsp?product=44&platform=Windows
Для создания PostScript-файла потребуется выбрать соответствующий принтер и
выдать команду меню Print to File (Печать в файл), которая обычно находится в диа-
логовом окне Print (Печать).
После этого большинство Windows-приложений создает файл с расширением
.ргп. Этот фаты должен быть PostScript-файлом. Возможно, имеет смысл его пере-
именовать, изменив расширение на .ps. Он должен быть пригодным для просмотра с
помощью GSview либо другой программы просмотра PostScript-файлов или для созда-
ния PDF-файла с помощью утилиты ps2pdf.
Следует иметь в виду', что различные драйверы принтеров создают PostScript-
вывод различного качества. Может оказаться, что некоторые созданные PostScript-
файлы вызывают ошибки при попытке их обработки утилитой ps2pdf. В этом случае
мы предлагаем воспользоваться другим драйвером печати.
768
Часть V. Реальные проекты на РНР и MySQL
Если вы намерены создавать лишь небольшое количество PDF-файлов, для выпол-
нения этой задачи может подойти онлайновая служба, предлагаемая компанией
Adobe. При уплате ежемесячной абонентской платы в размере $9,99 вы получаете
возможность загружать на сервер файлы в ряде различных форматов и затем полу-
чать из него готовые PDF-файлы. Эта служба успешно работала для созданного нами
сертификата, но она не позволяет выбирать опции, которые важны для рассматри-
ваемого проекта. Созданный PDF-файл будет сохранен в виде бинарного файла со
сжатием. Это обстоятельство существенно затрудняет его изменение.
Служба генерации PDF-документов доступна на сайте:
http://createpdf.adobe.сот/
Для тех, кто желает ее испытать, в этой службе имеется возможность бесплатного
пробного использования.
Бесплатный, использующий FTP-протокол, интерфейс к утилите ps2pdf входит
также и в состав пакета Net Distillery:
http://www.babinszki.сот/distiller/
Последний метод предполагает кодирование сертификата в формате XML и ис-
пользование XSLT-преобразований (XML Style Sheet Transformations — Преобразова-
ния таблиц стилей XML) для получения на выходе PDF или какого-то другого формата.
Этот метод требует глубоких знаний XSLT-преобразований и здесь не рассматривается.
Программное обеспечение для создания
PDF-документов с помощью кода
Поддержка создания PDF-документов доступна и в РНР. Существуют две библио-
теки функций, предназначенные для выполнения аналогичных задач.
PHP-функции используют библиотеку PDFlib, доступную по адресу:
http://www.pdflib.com/
Другая библиотека функций, ClibPDF, доступна по следующему адресу’:
http://www.fastio.com/
Обе библиотеки аналогичны. Они предоставляют API-интерфейс функций для ге-
нерации PDF-документов. Мы предпочитаем пользоваться библиотекой PDFlib, по-
скольку, как нам кажется, она обновляется и поддерживается на более регулярной
основе.
Следует отметить, что ни одна из этих библиотек не относится к свободному про-
граммному обеспечению. Обе допускают определенное бесплатное некоммерческое
использование, но требуют платы за лицензию, если вы собираетесь с их помощью
предоставлять коммерческие услуги.
Начинают появляться и свободно распространяемые библиотеки, например
FPDF. Правда, по возможностям FPDF еще далеко до коммерческих библиотек. Кро-
ме того, поскольку FPDF написана на РНР (а не на языке С в виде РНР-расширения),
она, к тому же, несколько медленнее PDFlib и ClibPDF. Загрузить библиотеку FPDF
можно по адресу http: / /wmi. fpdf.org/.
В этой главе мы будем пользоваться библиотекой PDFlib, так как она, вероятно,
является наиболее часто используемым расширением.
Глава 32. Генерация персонифицированных документов в PDF-формате
769
Проверить, установлена ли PDFlib в системе, можно, взглянув на результаты вы-
вода функции phpinfo(). В разделе pdf легко выяснить, включена ли поддержка
PDFlib, а также используемую версию этой библиотеки.
Для установки PDFlib потребуется также установить и библиотеку' TIFF, которая
доступна для загрузки на сайте:
http://www.libtiff.org/
и библиотеку7 JPEG, доступную по адресу:
ftp://ftp.uu.net/graphics/jpeg/
В Unix-системе эти компоненты программного обеспечения устанавливаются
обычным способом с помощью команд configure и make. При этом придется пере-
компилировать РНР с указанием ключа --with-pdf lib.
На сервере, который работает под управлением Windows, DLL-библиотека PDFlib
уже встроена в zip-архив системы РНР, поэтому достаточно только убрать коммента-
рий у этого расширения в файле php. ini.
Обзор решения
В рамках текущего проекта мы разработаем систему, которая будет приводить к
трем возможным результатам. Как видно на рис. 32.1, мы будем задавать контрольные
вопросы, оценивать ответы, а затем генерировать сертификат по одному из трех спо-
собов:
Мы будем генерировать RTF-документ из пустого шаблона.
Мы будем генерировать PDF-документ из пустого шаблона.
Мы будем генерировать PDF-документ программным путем за счет использова-
ния функций библиотеки PDFlib.
Краткий обзор файлов, используемых в проекте системы сертификации, приве-
ден в табл. 32.1.
Рис. 32.1. Наша сертификационная система будет генери-
ровать сертификаты одним из трех различных способов
770
Часть V. Реальные проекты на РНР и MySQL
Таблица 32.1. Файлы, используемые в приложении сертификации
Имя Tun Описание
index.html HTML-страница HTML-форма, содержащая экзаменационные вопросы.
score.php Приложение Сценарий для оценки ответов пользователей.
rtf.php Приложение Сценарий для генерации RTF-сертификата на основе шаблона.
pdf.php Приложение Сценарий для генерации PDF-сертификата на основе шаблона.
pdflib.php Приложение Сценарий для генерации PDF-сертификата с использованием функций библиотеки PDFlib.
signature.tif Изображение Растровое изображение подписи, которое будет включено в сертификат, сгенерирован- ный с использованием библиотеки PDFlib.
PHPCertification.rtf RTF-файл Шаблон сертификата в RTF-формате.
PHPCertification.pdf PDF-файл Шаблон сертификата в PDF-формате.
Итак, приступим к исследованию структуры и кода приложения.
Задание вопросов
Файл index.html довольно прост. Он должен содержать HTML-форму, запраши-
вающую пользователя его имя, и ответы на ряд вопросов. Скорее всего, в реальном
экзаменационном приложении эти ответы следовало бы получать из базы данных. Но
в данном случае нас интересует проблема создания сертификата, поэтому мы просто
жестко запрограммируем в HTML-коде несколько ответов.
Поле name является текстовым полем ввода. С каждым вопросом связаны три пе-
реключателя, позволяющие пользователю выбрать правильный, по его мнению, от-
вет. Форма содержит кнопку с изображением, служащую для отправки формы.
Код этой страницы можно найти в листинге 32.1.
Листинг 32.1. index.html — HTML-страница, содержащая экзаменационные вопросы
<html>
<body>
<hlxp align=" center">
<img src="rosette.gif" alt='"'>
Сертификация
<img src="rosette.gif" alt=" "x/px/hl>
<р>Вы также можете заработать наш широко признаваемый сертификат по РНР от
наиболее известного во всем мире Фиктивного Института Сертификации по РНР.</р>
<р>Просто дайте правильные ответы на перечисленные ниже вопросы:</р>
<form action="score.php" method=''post">
<р>Фамилия, имя <input type="text" name=''name"x/p>
<р>Какие действия выполняет РНР-оператор echo?</p>
<ol>
<lixinput type="radio" name="ql" value=”l">BbiBOflHT строки.</li>
<lixinput type="radio" name="ql" value="2">CyMMHpyeT два числа.</li>
<lixinput type="radio" name="ql" value="3">
Вызывает добрую фею, которая завершает за вас написание кода.</П>
Глава 32. Генерация персонифицированных документов в PDF-формате
771
</ol>
<р>Какие действия выполняет PHP-функция cos()?</p>
<ol>
<lixinput type="radio" name="q2" value="l">
Вычисляет косинус угла в радианах .с/И>
<lixinput type=”radio" name="q2" value=“2">
Вычисляет тангенс угла в радианах. c/li>
<lixinput type="radio" name="q2" value="3">
Такой PHP-функции не существует. </li>
</ol>
<р>Какие действия выполняет PHP-функция mail()?</p>
<ol>
clixinput type="radio" name="q3” value="l">
Отправляет сообщение по электронной почте.
<lixinput type="radio" name="q3" value="2“>
Получает новые почтовые сообщения.
clixinput type="radio" name="q3" value=“3">
Переключает PHP между мужским и женским режимами.
</о!>
<р align="center”xinput type="image“ src="certify-me.gif” bor-
der="0">c/p>
c/form>
c/body>
c/html>
Результат загрузки файла index. html в Web-браузер показан на рис. 32.2.
Рис. 32.2. Форма index.html предлагает посетителю
сайта ответить на экзаменационные вопросы
772
Часть V. Реальные проекты на РНР и MySQL
Оценка ответов
После того как пользователь отправит свои ответы на вопросы, выданные
index.html, их потребуется оценить и подсчитать общее количество баллов. Это вы-
полняется в сценарии score. php, код которого показан в листинге 32.2.
Листинг 32.2. score. php — сценарий для оценки результатов экзамена
<?php
// Создать короткие имена переменных
$ql = $_POST['ql'];
$q2 = $_POST['q2'];
$q3 = $_POST[q3'] ;
$name = $_POST['name'];
// Проверить, все ли данные получены
if($ql==’’||$q2==''||$q3==''||$name==')
{
echo '<hlxp align = centerximg src="rosette.gif" alt="">
Извините:
<img src="rosette.gif" alt=" "></px/hl>';
echo '<р>Вы должны ввести свою фамилию и ответить на все вопросы</р>';
}
else
{
// Просуммировать баллы
$score = 0;
if(Sql == 1) // правильный ответ на первый вопрос - 1
$score++;
if($q2 == 1) // правильный ответ на второй вопрос - 1
$score++;
if($q3 == 1) // правильный ответ на третий вопрос - 1
$score++;
// Преобразовать сумму баллов в проценты
$score = $score / 3 * 100;
if($score < 50)
{
// Посетитель не выдержал экзамен
echo 1 <hl align="center"ximg src=”rosette.gif" alt='"' />
Очень жаль:
<img src="rosette.gif" alt="" /></hl>';
echo '<р>Для того чтобы выдержать экзамен, вы должны набрать хотя бы
50%</р>';
}
else
{
// Создать строку, содержащую итог экзамена с точностью
//до одного десятичного разряда
$score = number_format($score, 1);
echo '<hl align="center"ximg src=”rosette.gif" alt="" />
Поздравляем!
<img src="rosette.gif" alt="" /></hl>';
Глава 32. Генерация персонифицированных документов в PDF-формате
773
echo "<р>Вы успешно сдали экзамен, $паше, ваш итог
составляет $score%.</р>";
// Вывести ссылки на сценарии, которые генерируют сертификаты
echo '<р>Пожалуйста, щелкните здесь, чтобы загрузить свой сертификат
в виде файла Microsoft Word (RTF).</p>‘;
echo '«form action=”rtf.php" method="post">';
echo '<center>
cinput type="image" src="certificate.gif" border="0">
</center>' ,-
echo '«input type="hidden" name="score" value="'.Sscore.';
echo '«input type="hidden" name="name" value="'.$name.'">';
echo '«/form>';
echo '<p> Пожалуйста, щелкните здесь, чтобы загрузить свой сертификат
в виде файла Portable Document Format (PDF).«/p>';
echo ’«form action="pdf.php" method=”post">';
echo '<center>
«input type="image" src="certificate.gif" border="0">
«/center>';
echo '«input type="hidden" name="score“ value=”'.$score.'">';
echo '«input type="hidden" name="name" value="',$name.'">';
echo '</form>';
echo '«р> Пожалуйста, щелкните здесь, чтобы загрузить свой сертификат
в виде файла Portable Document Format (PDF), сгенерированного
средствами PDFLib.</р>';
echo '«form action=”pdflib.php" method="post">';
echo '«center>
«input type="image" src = "certificate.gif" border=''0">
</center>';
echo '«input type="hidden" name="score" value="',$score.';
echo '«input type="hidden" name=''name" value=" ’ . $name. ' " > ' ;
echo '«/form>';
Этот сценарий будет выводить соответствующие уведомления в случаях, когда
пользователь не ответил на все вопросы или набрал менее определенного количества
баллов.
Если пользователь успешно ответил на вопросы, для него разрешается генерация
сертификата. Результат успешного посещения сайта можно видеть на рис. 32.3.
С этого момента посетителю доступны три возможности. Он может получить RTF-
сертификат или один из двух PDF-сертификатов. Сейчас мы последовательно рас-
смотрим сценарии, отвечающие за генерацию каждого из сертификатов.
Генерация RTF-сертификата
Теперь нам уже ничто не может помешать сгенерировать RTF-документ, записав
ASCII-текст в файл или в строковую переменную, однако для этого потребует изучить
еще один набор синтаксических конструкций.
774
Часть V. Реальные проекты на РНР и MySQL
Рис. 32.3. Сценарий score.php предоставляет пользова-
телям, успешно выдержавшим экзамен, возможность
сгенерировать сертификат одним из трех способов
Вот как выглядит очень простой RTF-документ:
{\rtfl
{\fonttbl {\f0 Arial;}{\fl Times New Roman;}}
\f0\fs28 ЗаголовокХраг
\fl\fs20 Это простой rtf-документ.\par
}
Этот документ устанавливает таблицу шрифтов, содержащую два шрифта: Arial, на
который можно будет ссылаться как на f 0, и Times New Roman, ссылка на который бу-
дет осуществляться посредством fl. Затем документ записывает строку Заголовок, ис-
пользуя шрифт fo (Arial) размером 28 (14 пунктов). Управляющая последовательность
\раг указывает на конец абзаца. Затем выполняется запись строки Это простой rtf-
документ с использованием шрифта fl (Times New Roman) размером 20 (10 пунктов).
Похожий документ можно было бы сгенерировать вручную, однако РНР не содер-
жит никаких встроенных функций, которые могли бы упростить выполнение столь
трудных задач, как внедрение графических изображений. К счастью, во многих доку-
ментах структура, стиль и значительная часть текста являются статическими, и лишь
небольшие фрагменты изменяются от одного физического лица к другому. Более эф-
фективный способ генерации документа предполагает использование шаблона.
Сложный документ, подобный показанному на рис. 32.4, можно легко создать в
текстовом процессоре.
Приведенный шаблон содержит заполнители наподобие <<NAME>> для пометки
мест вставки динамических данных. Как конкретно выглядят эти заполнители — не
особенно важно. Мы используем понятное описание, помещенное между двумя набо-
рами угловых скобок (« и »). Важно выбрать заполнители, случайное появление
которых в остальной части документа маловероятно. Создание макета шаблона суще-
Глава 32. Генерация персонифицированных документов в PDF-формате
775
ственно упростится, если заполнители будут иметь приблизительно такую же длину,
как и данные, которыми они будут замещаться.
Рис. 32.4. С помощью текстового процессора можно легко создать
сложный и привлекательный шаблон
В этом документе используются заполнители <<NAME>>, <<Name>>, <<score>> и
<<mm/dd/yyyy>>. Обратите внимание, что в шаблоне присутствуют и NAME, и Name,
поскольку для их замещения предполагается использовать метод, чувствительный к
регистру символов.
Теперь, когда шаблон создан, необходимо написать сценарий для его персонифи-
кации. Этот сценарий называется rtf . php и его код можно найти в листинге 32.3.
Листинг 32.3. rtf .php — сценарий для создания персонифицированного
сертификата в RTF-формате
<?php
// Создать короткие имена переменных
$name = $_POST['name'];
$score = $_POST['score'];
// Убедиться, что все необходимые параметры присутствуют
if) !$name || !$score )
{
echo '<Ы>Ошибка:</Ы>Страница вызвана некорректно';
}
else
{
// Сгенерировать заголовки, которые упростят браузеру
// выбор требуемого приложения для визуализации
header) 'Content-Type: application/msword' );
header) 'Content-Disposition: inline, filename=cert.rtf’);
$date = date) 'F d, Y' );
776
Часть V. Реальные проекты на РНР и MySQL
// Открыть файл шаблона
$f ilename = 'PHPCertification.rtf;
$output = file_get_contents($filename);
// Заменить заполнители в шаблоне требуемыми данными
$output = str_replace( '<<NAME>>', strtoupper( $name ), $output );
$output = str_replace( ’<<Name»', $name, $output );
$output = str_replace( ’<<score>>', $score, $output );
$output = str_replace( '<<mm/dd/yyyy>>', $date, $output );
// Отправить сгенерированный документ в браузер
echo $output;
Этот сценарий выполняет некоторую общую проверку на предмет присутствия
ошибок в данных, дабы убедиться в получении всех сведений о пользователе, после
чего начинается создание сертификата.
Сценарий создает вывод в виде RTF-файла, а не HTML-файла, поэтому о данном
факте необходимо предупредить браузер пользователя. Важно, чтобы браузер мог
предпринять попытку открытия файла с помощью подходящего приложения или
отображал диалоговое окно типа Save As... (Сохранить как...), если он не смог распо-
знать расширение .rtf.
MIME-тип выводимого файла для отправки соответствующего НТТР-заголовка
указывается с помощью PHP-функции header () следующим образом:
header('Content-Type: application/msword');
header('Content-Disposition: inline, filename=cert.rtf');
Первый заголовок уведомляет браузер, что ему отправляется документ Microsoft
Word (наличие этого вспомогательного приложения для открытия RTF-файла наибо-
лее вероятно, хотя это и не всегда так).
Второй заголовок указывает браузеру автоматически отобразить содержимое файла
с предлагаемым именем cerf. rtf. Именно это имя файла по умолчанию пользователь
будет видеть, если попытается сохранить файл из среды своего браузера.
После того .как заголовки отправлены, мы открываем и считываем RTF-файл шаб-
лона в переменную $output и с помощью функции str_replace() заменяем заполни-
тели фактическими данными, которые должны появляться в файле. Следующая строка:
$output = str_replace( '<<Name>>', $name, $output );
заменит все вхождения заполнителя <<Name>> содержимым переменной $name.
После выполнения всех подстановок остается только передать результат для вывода в
окне браузера. Пример результата выполнения этого сценария показан на рис. 32.5.
Этот подход работает очень хорошо. Вызов функции str_replace () выполняется
достаточно быстро, даже притом, что наш шаблон и, стало быть, содержимое пере
менной $output характеризуется значительной длиной. С точки зрения этого при-
ложения основная проблема состоит в том, что для печати сертификата пользовате-
лю придется его загрузить в текстовый процессор. Вероятно, это будет служить
своего рода толчком к модификации документа. RTF-формат не позволяет получит;,
документ, предназначенный только для чтения.
Глава 32. Генерация персонифицированных документов в PDF-формате
777
Рис. 32.5. Сценарий rtf .php генерирует сертификат из шаблона в
RTF-формате
Генерация PDF-сертификата из шаблона
Процесс генерации PDF-сертификата из шаблона во многом подобен процессу,
который подробно описывался ранее. Основная разница состоит в том, что при соз-
дании PDF-файла некоторые заполнители могут быть спутаны с кодами форматиро-
вания, в зависимости от применяемой версии Acrobat. Например, если взглянуть на
созданный (в текстовом редакторе) шаблон сертификата, несложно заметить, что
теперь заполнители выглядят следующим образом:
«N)-13 (АМЕ)-10 (>)-6 (>
<<Na) -9 (ш) 0 (е) -18 (»
<)-11(<)1(sc)-17(or)-6(е)-6(>)-11(>
<) -11 (<) 1 (m)-12 (m) 0 (/й)-6 (d)-19 (/ ) 1 (уу)-13 (уу)-13 (»
Если вы просмотрите файл, то наверняка увидите, что, в отличие от RTF, этот
формат не является столь же читабельным.
Примечание
Формат PDF-файла шаблона может варьироваться в зависимости от используемой версии Ac-
robat или другого инструмента генерации PDF. Предложенный в данном примере код может не
s работать с вашими собственными шаблонами. В этом случае потребуется должным образом
Ц скорректировать код. Если проблемы продолжают возникать, воспользуйтесь примером при-
менения библиотеки PDFlib, который приведен далее в главе.
Существует несколько способов выхода из подобной ситуации. Можно просмот-
реть каждый из заполнителей и удалить коды форматирования. Это весьма незначи-
тельно сказалось бы на конечном виде документа, поскольку внедренные в предыду-
щий шаблон коды указывают, сколько места должно быть оставлено между буквами
заполнителей, которые в любом случае будут замещаться. Однако при таком подходе
778
Часть V. Реальные проекты на РНР и MySQL
придется вручную отредактировать PDF-файл и повторять это при каждом его изме-
нении или обновлении. Это не особенно трудно, когда приходится иметь дело только
с четырьмя заполнителями, но превращается в настоящий кошмар, когда, например,
существует несколько документов с множеством заполнителей и необходимо изме-
нить заголовки во всех документах.
Упомянутой проблемы можно избежать, взяв на вооружение другую технологию.
Можно с помощью Adobe Acrobat создать PDF-форму, подобную HTML-форме, с пус-
тыми именованными полями. Затем можно воспользоваться PHP-сценарием для соз-
дания файла в FDF-формате (Forms Data Format — формат данных форм), который
представляет собой набор данных, объединяемых с шаблоном. FDF-файлы также
можно создавать с использованием библиотеки PHP-функций FDF: в частности, вы-
звать функцию fdf_create () для создания файла, функцию fdf_set_value () — для
определения значений полей и функцию fdf_setf ile() — для определения связан-
ного с шаблоном файла формы. Затем этот файл можно передать обратно в браузер с
соответствующим MIME-типом, в данном случае vnd.fdf, и подключаемый модуль
Acrobat Reader браузера должен подставить данные в форму.
Это весьма искусный способ решения задачи, тем не менее, с ним связаны два ог-
раничения. Во-первых, предполагается, что у вас имеется копия Acrobat Professional
(полная версия, а не бесплатная программа чтения, и даже не версия Standard). Во-
вторых, довольно-таки трудно заместить текст, который является встроенным, а не
выглядящим как поле формы. Это может оказаться либо трудным, либо не очень, в
зависимости от того, что вы пытаетесь сделать. Мы интенсивно использовали гене-
рацию PDF-документов для создания писем, в которых очень многое должно заме-
щаться непосредственно в тексте. FDF-файлы не особенно хорошо подходят для этой
цели. Однако, при онлайновом заполнении, например, налоговой декларации, это не
составит особой проблемы.
Более подробную информацию по FDF-формату можно получить на сайте компа-
нии Adobe:
http:I/partners.adobe.com/asn/developer/acrosdk/forms.html
Если решено использовать именно этот подход, имеет смысл также посмотреть
документацию по FDF. которая входит в состав руководства по РНР:
http://www.php.net/manual/en/ref.fdf.php
Теперь давайте обратимся к нашему решению ранее указанной проблемы, которое
предполагает использование PDF.
Заполнители в PDF-файле все же можно найти и заменить, если известно, что ко-
ды дополнительного форматирования состоят исключительно из дефисов, цифр и
круглых скобок и, следовательно, могут быть сопоставлены с регулярным выражени-
ем. Мы написали функцию pdf_replace () для автоматической генерации сопостав-
ляющего регулярного выражения для заполнителя и для замены этого заполнителя
соответствующим текстом.
Следует отметить, что в некоторых версиях Acrobat заполнители представ-
ляют собой простой текст, поэтому их можно заменять с помощью функции
str_replace (), как было показано ранее.
Глава 32. Генерация персонифицированных документов в PDF-формате
779
За исключением этого добавления, код для генерации сертификата с использова-
нием PDF-шаблона во многом подобен RTF-версии. Этот сценарий можно найти в
листинге 32.4.
Листинг 32.4. pdf .php — сценарий для создания персонифицированного
PDF-сертификата с использованием шаблона
<?php
set_time_limit( 180 ); // этот сценарий может оказаться достаточно медленным
// Создать короткие имена переменных
$name = $_POST['name'];
$score = $_POST['score'];
function pdf_replace( $pattern, $replacement, $string )
{
$len = strlen ( $pattern )
$regex = ' ' ;
for ( $i = 0; $i<$len; $i++ )
{
$regex .= $pattern[$i];
if ($i<$len-l)
$regex .= '(\)-?[0-9]+\()
}
return ereg_replace ( $regex, $replacement, $string );
}
if(1Sname||!$score)
{
echo '<Ы>Ошибка:</М>Страница вызвана некорректно’;
}
else
{
// Сгенерировать заголовки, которые упростят браузеру выбор
// требуемого приложения для визуализации
header( 'Content-Disposition: filename=cert.pdf');
header( 'Content-Type: application/pdf' );
$date = date( 1F d, Y' );
// Открыть файл шаблона
$filename = 'PHPCertification.pdf;
$output = file_get_contents($filename);
// Заменить заполнители в шаблоне требуемыми данными
$output = pdf_replace( '<<NAME>>', strtoupper( $name ), $output );
$output = pdf_replace( '<<Name>>', $name, $output );
$output = pdf_replace( '<<score»', $score, $output );
$output = pdf_replace( '<<mm/dd/yyyy>>', $date, $output );
// Отправить сгенерированный документ в браузер
echo $output;
}
Приведенный в листинге 32.4 сценарий создает персонифицированную версию
PDF-документа. Документ, показанный на рис. 32.6, будет надежно печататься в раз-
780
Часть V. Реальные проекты на РНР и MySQL
личных системах, и получателю довольно-таки трудно будет его модифицировать ли-
бо редактировать. Как видите, PDF-документ, показанный на рис. 32.6. выглядит поч-
ти так же, как RTF-документ, изображенный на рис. 32.5.
Рис. 32.6. Сценарий pdf .php генерирует сертификат на
основе шаблона в PDF-формате
Единственная проблема при использовании этого подхода состоит в том, что код
работает относительно медленно, поскольку в нем применяются регулярные выра-
жения. Регулярные выражения обрабатываются значительно медленнее, нежели
функция str_replace (). которой можно было пользоваться в RTF-версии.
Если предстоит сопоставить большое количество заполнителей или нужно сгене-
рировать много таких документов на одном и том же сервере, возможно, стоит отдать
предпочтение другим подходам. В случае более простого шаблона проблема стояла
бы не так остро. Дело в том, что в нашем файле значительную часть данных пред-
ставляют изображения.
Генерация PDF-документа с использованием
библиотеки PDFlib
Библиотека функций PDFlib предназначена для генерации динамических PDF-
документов для Web. Строго говоря, она является не частью РНР, а отдельной биб-
лиотекой, содержащей множество функций, предназначенных для вызова из широ-
кого спектра языков программирования. Существуют сборки этой библиотеки для
языков С, C++, Java, Perl, Python, Tel и ActiveX/СОМ.
Глава 32. Генерация персонифицированных документов в PDF-формате
781
Начиная с версии РНР 4.0.5, библиотека PDFlib стала официально поддерживать-
ся компанией PDFlib GmbH. Это значит, что вы сможете найти ее описание либо в
документации по РНР, которая доступна по адресу:
http://www.php.net/en/manual/ref.pdf.php
либо загрузить оригинальную документацию по этой библиотеке из сайта pdf 1 ib. com.
Простейший сценарий для PDFlib
После того как РНР инсталлирован с включенной поддержкой PDFlib, можно
протестировать его установку с помощью простейшей программы наподобие тради-
ционного примера вывода приветствия, код которого показан в листинге 32.5.
Листинг 32.5. testpdf. php — классический пример вывода приветствия,
использующий PDFlib в среде РНР
<?php
// Создать pdf-документ в памяти
$pdf = pdf_new();
pdf_open_file($pdf, ' ') ;
pdf_set_info($pdf, "Creator”, "pdftest.php");
pdf_set_info($pdf, "Author", "Luke Welling and Laura Thomson");
pdf_set_info($pdf, "Title", "Hello World (PHP)");
11 Выбрать печатный лист стандарта US letter с размерами 11 х 8.5 дюймов
// и разрешением приблизительно 72 пункта на дюйм
pdf_begin__page ($pdf, 8.5*72, 11*72);
// Добавить закладку
pdf_add_bookmark($pdf, 'Страница 1', 0, 0) ;
$font = pdf_findfont($pdf, 'Times-Roman', 'host', 0);
pdf_setfont($pdf, $font, 24);
pdf_set_text_pos($pdf, 50, 700);
// Вывести текст
pdf_show($pdf,'Приветствую вас !');
pdf_continue_text($pdf,'(говорит PHP)');
// Завершить документ
pdf_end—page($pdf);
pdf_close($pdf);
$data = pdf_get_buffer($pdf);
// Сгенерировать заголовки, чтобы браузер смог выбрать нужное приложение
header('Content-Type: application/pdf') ;
header('Content-Disposition: inline; filename=test.pdf');
header('Content-Length: ' . strlen($data));
// Вывести PDF
echo $data;
?>
782
Часть V. Реальные проекты на РНР и MySQL
Наиболее вероятная ошибка, которую может выдать этот сценарий, выглядит
приблизительно так:
Fatal error: Call to undefined function pdf_new()
in c:\program files\apache group\Apache\htdocs\phpmysql3e\chapter32\testpdf.php
on line 4
Критическая ошибка: Вызов неопределенной функции pdf_new()
в c:\program files\apache group\Apache\htdocs\phpmysql3e\chapter32\testpdf.php
в строке 6
Это означает, что расширение PDFlib не было скомпилировано или включено в
РНР.
Установка PDFlib достаточно проста, хотя некоторые моменты могут различаться
в зависимости от конкретных версий РНР и PDFlib. Для ознакомления с некоторыми
соображениями по поводу инсталляции обратитесь к замечаниям со стороны пользо-
вателей, которые находятся на странице PDFlib онлайнового руководства по РНР.
После установки и успешного выполнения этого сценария в своей системе можно
приступить к его исследованию.
Следующие строки кода:
$pdf = pdf_new();
pdf_open_file($pdf, '');
инициализируют PDF-документ в памяти.
Функция pdf_set_info () позволяет снабдить документ дескриптором, содержа-
щим тему7, заголовок, имя создателя, автора, список ключевых слов и одно дополни-
тельное поле, определяемое пользователем.
В приведенном ниже фрагменте кода мы определяем создателя документа, его ав-
тора и заголовок. Обратите внимание, что все шесть информационных полей PDF-
документа являются необязательными.
pdf_set_info($pdf, "Creator", "pdftest.php”);
pdf_set_info($pdf, "Author", “Luke Welling and Laura Thomson”);
pdf_set_info($pdf, "Title", "Hello World (PHP)");
PDF-документ состоит из набора страниц. Чтобы начать новую страницу’, следует
обратиться к функции pdf_begin_page (). Как и идентификатору, возвращаемому
функцией pdf_open (), функции pdf_begin_page () требуется передать размеры стра-
ницы. Каждая страница в документе может иметь свои размеры, однако если только
на то не существует веских причин, следует указывать одинаковые размеры страниц.
И для размеров страниц, и для определения координат положений на странице в
PDFlib используются пункты (points). Например, страница формата А4 имеет разме-
ры, равные приблизительно 595 на 842 пунктов, а страница формата U.S. letter — 612
на 792 пунктов. Это означает, что следующая строка:
pdf_begin_page($pdf, 8.5*72, 11*72);
создает внутри документа страницу, размеры которой соответствуют стандартному
формату U.S. letter.
PDF-документ не обязательно должен быть печатным. В документ могут быть вне-
дрены многие возможности PDF, такие как гиперссылки и закладки. Функция
pdf_add_bookmark () добавляет закладку в структуру документа. Закладки в документе
Глава 32. Генерация персонифицированных документов в PDF-формате
783
будут отображаться в отдельной панели окна Acrobat Reader, позволяя переходить
непосредственно к важным разделам.
Строка:
pdf_add_bookmark($pdf, 'Страница 1', 0, 0);
добавляет закладку, помеченную как “Страница 1”, которая ссылается на текущую
страницу.
Доступные в системе шрифты варьируются от одной операционной системы к
другой, и даже от одного компьютера к другому. Набор основных шрифтов, рабо-
тающих с любой программой чтения PDF-документов, обеспечит единообразие ре-
зультатов. Вот как выглядит перечень 14 основных шрифтов:
Courier
Courier-Bold
Courier-Oblique
Courier-BoldOblique
Helvetica
Helvetica-Bold
Helvetica-Oblique
Helvetica-BoldOblique
Times-Roman
Times-Bold
Times-Italic
Times-Boldltalic
Symbol
ZapfDingbats
В документы можно внедрять и другие шрифты, не входящие в представленный
список, но это приведет к увеличению размеров файла и может противоречить ли-
цензионному соглашению на использование того или иного шрифта. Шрифт, его
размер и кодировку символов можно выбрать следующим образом:
$font = pdf_findfont($pdf, 'Times-Roman', 'host', 0);
pdf_setfont($pdf, $font, 24);
Размеры шрифтов задаются в пунктах. Мы выбрали кодировку символов, уста-
новленную на хосте (' host' ). Допустимыми значениями этого параметра являются
'winansi1, 'builtin', 'macroman', 'ebcdic' и 'host'. Вот что они означают:
winansi — кодировка символов в соответствии со стандартом ISO 8859-1 плюс
специальные символы, добавленные компанией Microsoft, например, символ
денежной единицы евро.
builtin — использование кодировки, встроенной в шрифт. Обычно использу-
ется со шрифтами и символами, отличными от латинских.
macroman — кодировка Mac Roman. Набор символов, устанавливаемый по умол-
чанию на компьютерах Macintosh.
784
Часть V. Реальные проекты на РНР и MySQL
ebcdic — кодировка EBCDIC, используемая в системах IBM AS/400.
host — автоматически выбирает macronian для системы Macintosh, ebcdic для
системы, использующей кодировку EBCDIC, и winansi во всех остальных слу-
чаях.
Если применение специальных символов не требуется, выбор кодировки роли не
играет.
PDF-документ не похож на HTML-документ или на документ текстового процессо-
ра. Текст не начинается просто с верхнего левого угла и не продолжается при необ-
ходимости в других строках. Необходимо выбирать местоположение каждой строки
текста. Как уже упоминалось, для указания местоположения в PDF-документе исполь-
зуются пункты. Начало координат (х, у, равные [0, 0]) располагается в нижнем левом
углу страницы.
Если используемая страница имеет размеры 612 на 792 пункта, то точка (50, 700)
находится на расстоянии приблизительно двух третей дюйма от левого края страни-
цы и около одного и одной трети дюйма от верхнего края. Чтобы расположить текст
в этой позиции, применяется следующая строка:
pdf_set_text_pos($pdf, 50, 700);
И, наконец, определив параметры страницы, можно разместить на ней некото-
рый текст. Для добавления текста в текущую позицию с использованием текущего
шрифта служит функция pdf_show ().
Приведенная ниже строка:
pdf_show($pdf,'Приветствую вас!');
добавляет в документ текст 'Приветствую вас! '.
Для перехода на следующую строку и записи нового текста используется функция
pdf_continue_text (). Таким образом, следующий код приводит к добавлению в до-
кумент строки '(говорит РНР)':
pdf_continue_text($pdf,'(говорит РНР)');
Точное расположение этого текста будет зависеть от выбранного шрифта и его
размера.
Если вместо строки текста или отдельной фразы необходимо разместить несколь-
ко абзацев текста, более полезной будет функция pdf _show_boxed (). Она позволяет
объявить текстовое окно и заполнить его конкретным текстом.
По завершении добавления элементов на страницу следует вызвать функцию
pdf_end_page():
pdf_end_page($pdf);
Закончив создание всего PDF-документа, его необходимо закрыть с помощью
функции pdf_close (). Если вы генерируете какой-то файл, по завершении его также
следует закрыть.
Следующая строка:
pdf_close($pdf);
завершает генерацию рассматриваемого документа.
Глава 32. Генерация персонифицированных документов в PDF-формате
785
Теперь документ можно отправить в браузер:
$data = pdf_get_buffer($pdf);
// Сгенерировать заголовки, чтобы браузер смог выбрать нужное приложение
header('Content-Type: application/pdf') ;
header('Content-Disposition: inline; filename=test.pdf');
header('Content-Length: ' strlen($data));
11 Вывести PDF
echo $data;
При желании полученные данных могут быть записаны также и на диск. Библио-
тека PDFlib позволяет это делать, передавая имя файла во втором параметре функции
pdf_open_f ile (). На момент написания книги эта возможность работала с ошибками
в среде Windows (РНР 5.0.0). Если необходима запись данных на диск, ее можно вы-
полнить вручную.
Этот пример был построен на основе примера на языке С, который приведен в
документации по PDFlib, и должен послужить полезной отправной точкой.
Следует отмстить, что некоторые параметры функций PDFlib, обозначенные в ру-
ководстве по РНР как необязательные, в ряде версий PDFlib таковыми не являются.
Документ, который потребуется создать для сертификата, более сложен и содержит
рамку, а также векторное и растровое изображения. При использовании двух других
технологий мы добавили эти элементы в текстовом процессоре. В случае подхода с
библиотекой PDFlib их необходимо добавить вручную.
Генерация сертификата с помощью PDFlib
Чтобы использовать PDFlib в этом проекте, нам пришлось пойти на некоторые
компромиссы. Хотя почти наверняка можно продублировать сертификат, который
использовался ранее, для создания макета документа гораздо большие усилия потре-
бовались бы при генерации и позиционировании каждого элемента вручную, чем при
использовании инструмента, подобного текстовому процессору Microsoft Word.
В этом случае применяется тот же текст, что и ранее, включая красную розетку и
растровое изображение подписи, однако мы не планируем повторять сложную рамку.
Полный исходный текст этого сценария можно найти в листинге 32.6.
Листинг 32.6. pdf lib. php — генерация сертификата с использованием библиотеки PDFlib
<?php
// Создать короткие имена переменных
$name = $_POST['name'];
$score = $_POST['score'];
if(!$name||!$score)
{
echo '<Ь1>0шибка:</hl>3Ta страница вызвана некорректно';
exit ;
1
else
{
$date = date( 'F d, Y' );
786
Часть V. Реальные проекты на РНР и MySQL
// Создать pdf-документ в оперативной памяти
$pdf = pdf_new();
pdf_open_file($pdf, ' ') ;
// Установить имя шрифта для дальнейшего использования
Sfontname = 'Times-Roman';
// Определить размеры страницы в пунктах.
// Страница US letter имеет размеры 11 х 8.5 дюймов и
// разрешение приблизительно 72 пункта на дюйм
Swidth = 11*72;
Sheight = 8.5*72;
pdf_begin_page(Spdf, $width, Sheight);
// Нарисовать рамки
$inset =20; // расстояние между рамкой и краем страницы
$border =10; // толщина линии основной рамки
$inner =2; // промежуток внутри рамки
// Нарисовать внешнюю рамку
pdf_rect(Spdf, $inset-$inner,
$inset-$inner,
$width-2 *($inset-$inner),
$height-2*($inset-$inner));
pdf_stroke(Spdf);
// Нарисовать основную рамку толщиной Sborder пунктов
pdf_setlinewidth($pdf, $border);
pdf_rect($pdf, $inset+$border/2,
$inset+$border/2,
$width-2 *($inset+$border/2),
$height-2*($inset+$border/2));
pdf_stroke(Spdf);
pdf_setlinewidth(Spdf, 1.0);
// Нарисовать внутреннюю рамку
pdf_rect(Spdf, $inset+$border+$inner,
$inset+$border+$inner,
Swidth-2*($inset+$border+$inner),
$height-2*($inset+$border+$inner));
pdf_stroke(Spdf);
// Добавить заголовок
Sfont = pdf_findfont($pdf, Sfontname, 'host', 0);
if (Sfont)
pdf_setfont($pdf, $font, 48);
Sstartx = (Swidth - pdf_stringwidth($pdf, 'PHP Certification',
Sfont, 48))/2;
pdf_show_xy(Spdf, 'PHP Certification', Sstartx, 490);
// Добавить текст
Sfont = pdf_findfont(Spdf, Sfontname, 'host', 0);
if (Sfont)
pdf_setfont($pdf, $font, 26);
Sstartx = 70;
pdf_show_xy(Spdf, 'This is to certify that:', Sstartx, 430);
pdf_show_xy(Spdf, strtoupper(Sname), $startx+90, 391);
Глава 32. Генерация персонифицированных документов в PDF-формате
787
$font = pdf_findfont($pdf, $fontname, 'host', 0);
if ($font)
pdf_setfont($pdf, $font, 20);
pdf_show_xy($pdf, 'has demonstrated that they are certifiable
'by passing a rigorous exam', $startx, 340);
pdf_show_xy($pdf, 'consisting of three multiple choice questions.',
Sstartx, 310);
pdf_show_xy($pdf, "$name obtained a score of $score"., $startx, 260);
pdf_show_xy($pdf, 'The test was set and overseen by the ' , $startx, 210);
pdf_show_xy($pdf, 'Fictional Institute of PHP Certification', $startx, 180);
pdf_show_xy($pdf, "on $date.”, $startx, 150);
pdf_show_xy($pdf, 'Authorized by:', $startx, 100);
// Добавить растровое изображение подписи.
// Если необходимо, измените путь к файлу подписи на соответствующий
$path = 'С:/Program Files/Apache Group/Apache/htdocs/phpmysql3e/chapter32/';
// Использовать GIF-версию, поскольку в случае PNG библиотека
// PDFlib для Windows, похоже, сбоит
$signature = pdf_open_image_file($pdf, 'gif', $path.'signature.gif',
'mask', 0);
pdf_place_image($pdf, Jsignature, 200, 75, 1);
pdf_closeimage($pdf, $signature);
11 Настроить цвета для вывода розетки
pdf_setcolor($pdf, 'fill', 'rgb', 0, 0, .4, 0); 11 темно-синий
pdf_setcolor($pdf, 'stroke', 'rgb', 0, 0, 0, 0); // черный
// Нарисовать первую ленточку
pdf_moveto($pdf, 630, 150)
pdf_lineto($pdf, 610, 55) ;
pdf_lineto($pdf, 632, 69) ;
pdf_lineto($pdf, 646, 49) ;
pdf_lineto($pdf, 666, 150)
pdf_closepath($pdf);
pdf_fill($pdf);
// Нарисовать контур первой ленточки
pdf_moveto($pdf, 630, 150)
pdf_lineto($pdf, 610, 55) ;
pdf_lineto($pdf, 632, 69) ;
pdf_lineto($pdf, 646, 49) ;
pdf_lineto($pdf, 666, 150)
pdf_closepath($pdf);
pdf_stroke($pdf) ;
// Нарисовать вторую ленточку
pdf_moveto($pdf, 660, 150);
pdf_lineto($pdf, 680, 49);
pdf_lineto($pdf, 695, 69);
pdf_lineto($pdf, 716, 55);
pdf_lineto($pdf, 696, 150);
pdf_closepath($pdf);
pdf_fill($pdf);
788
Часть V. Реельные проекты на РНР и MySQL
// Нарисовать контур второй ленточки
pdfjnoveto($pdf, 660, 150);
pdf_lineto($pdf, 680, 49);
pdf_lineto($pdf, 695, 69);
pdf_lineto($pdf, 716, 55);
pdf_lineto($pdf, 696, 150);
pdf_closepath($pdf);
pdf_stroke($pdf);
pdf_setcolor($pdf, 'fill', 'rgb', .8, 0, 0, 0); // красный
// Нарисовать розетку
draw_star(665, 175, 32, 57, 10, $pdf, true);
11 Нарисовать контур розетки
draw_star(665, 175, 32, 57, 10, $pdf, false);
I/ Завершить формирование страницы и подготовить ее к выводу
pdf_end_page($pdf);
pdf_close($pdf);
$data = pdf_get_buffer($pdf);
// Сгенерировать заголовки, которые упростят браузеру выбор
// требуемого приложения для визуализации
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename=certificate.pdf');
header(1 Content-Length: ' . strlen($data));
/I Вывести PDF-документ
echo $data;
}
function draw_star($centerx, $centery, $points, $radius,
$point_size, Spdf, $filled)
{
$inner_radius = $radius-$point_size;
for ($i = 0; $i<=$points*2; $i++ )
{
$angle= ($i*2*pi())/($points*2) ;
if($i%2)
{
$x = $radius*cos($angle) + $centerx;
$y = $radius*sin($angle) + $centery;
}
else
{
$x = $inner_radius*cos($angle) + $centerx;
$y = $inner_radius*sin($angle) + $centery;
}
if($i==0)
pdf_moveto($pdf, $x, Sy);
else if($i==$points*2)
pdf_closepath($pdf);
else
pdf_lineto($pdf, $x, $y);
Глава 32. Генерация персонифицированных документов в PDF-формате
789
if($filled)
pdf_fill_stroke($pdf);
else
pdf_stroke($pdf) ;
Сгенерированный этим сценарием сертификат показан на рис. 32.7. Как видите,
он очень похож на созданные ранее сертификаты, за исключением того, что его рам-
ка проще, а розетка выглядит несколько иначе. Это связано с тем, что они были на-
рисованы непосредственно в документе, а не с использованием существующего файла
с изображением.
Рис. 32.7. Сценарий pdflib.php генерирует сертификат в
PDF-документе
Давайте рассмотрим некоторые части данного сценария, отличающиеся от при-
веденных ранее примеров.
Посетителям необходимо, чтобы в сертификате отображались их персональные
данные, поэтому документ будет создаваться в памяти, а не в файле. Если бы он был
записан в файл, следовало бы позаботиться о механизмах создания уникальных имен
файлов, предотвратить подглядывание чужих сертификатов и определить способ
удаления устаревших файлов сертификатов с целью освобождения пространства на
жестком диске сервера. Для создания документа в памяти вызывается функция
pdf_open () без параметров, за которой следует вызов функции pdf_open_f ile ():
$pdf = pdf_new();
pdf_open_file($pdf, '');
790
Часть V. Реальные проекты на РНР и MySQL
Упрощенная рамка будет состоять из трех контуров: одного жирного и двух тон-
ких, один из которых располагается снаружи основного, а другой — внутри. Все эти
контуры рисуются в виде прямоугольников.
Для размещения рамок так, чтобы можно было легко изменять размеры страницы
или внешний вид рамок, позиции всех рамок будут основаны на уже существующих
переменных $width и $height, а также на ряде других: $inset, $border и $inner. Пе-
ременная $ inset будет использоваться для указания расстояния между рамкой и кра-
ем страницы в пунктах, $border — для указания толщины основной рамки, а $ inner —
для указания ширины промежутка между основной и тонкими рамками.
Если читателям ранее доводилось выполнять рисование с помощью другого гра-
фического API-интерфейса, рисование с помощью PDFlib преподнесет несколько
сюрпризов. Если вы не прочли главу 21, возможно, стоит сделать это прямо сейчас,
поскольку рисование изображений с помощью библиотеки gd очень похоже на рисо-
вание с помощью PDFlib.
Тонкие рамки не представляют особой сложности. Для создания прямоугольника
мы используем функцию pdf_rect (), которая в качестве параметров требует переда-
чи идентификатора PDF-документа, координат х и у нижнего левого угла прямоуголь-
ника, а также его ширины и высоты. Поскольку мы пытаемся обеспечить гибкость
макета, эти значения вычисляются на основе определенных нами переменных.
pdf_rect($pdf, $inset-$inner,
$inset-$inner,
$width-2*($inset-$inner),
$height-2*($inset-$inner)) ;
Вызов функции pdf_rect() определяет контур формы прямоугольника. Чтобы
вычертить эту форму, потребуется обратиться к функции pdf_stroke ():
pdf_stroke($pdf);
Для рисования основной рамки необходимо указать толщину линии. По умол-
чанию толщина линии составляет 1 пункт. Следующее обращение к функции
pdf_setlinewidth() устанавливает ее равной $border пунктов (в данном случае— 10):
pdf_setlinewidth($pdf, $border);
После установки толщины мы снова создаем прямоугольник с помощью функции
pdf_rect () и вызываем функцию pdf_stroke (), чтобы его нарисовать:
pdf_rect($pdf, $ inset+$border12,
$inset+$border/2,
$width-2*($inset+$border/2),
$height-2*($inset+$border/2)) ;
pdf_stroke($pdf);
После того как толстая линия нарисована, нужно не забыть снова установить тол-
щину линии равной 1 пункту:
pdf_setlinewidth($pdf, 1.0);
Мы используем функцию pdf_show_xy () для размещения каждой строки текста
внутри сертификата. Для большинства строк текста применяется настраиваемая ле-
вая граница ($startx) в качестве координаты х и выбранное на глаз значение в каче-
Глава 32. Генерация персонифицированных документов в PDF-формате
791
стве координаты у. Поскольку требуется, чтобы заголовок располагался по центру
относительно боковых сторон страницы, для размещения его левого края нужно
знать ширину заголовка. Эту ширину можно получить с помощью функции
pdf_stringwidth (). Следующий вызов функции:
pdf_stringwidth($pdf, 'РНР Certification', $font, 48);
вернет ширину строки ' РНР Certification' для текущего шрифта и его размера.
Как это было сделано с другими версиями сертификата, подпись будет вставлена в
него в виде сканированного растрового изображения. Следующие три оператора:
$signature = pdf_open_image_file($pdf, 'gif', $path.'signature.gif',
'mask', 0);
pdf_place_image($pdf, $signature, 200, 75, 1);
pdf_close_image($pdf, $signature);
открывают GIF-файл, содержащий изображение подписи, добавляют изображение в
указанную позицию на странице и, наконец, закрывают GIF-файл. Можно использо-
вать и другие типы файлов. (Мы решили использовать GIF, поскольку поддержка
PNG под Windows на момент написания книги содержала ошибки.) Единственный
параметр, который, возможно, не особенно понятен, — это пятый параметр функции
pdf_place_image (). Эта функция не ограничивается вставкой изображения с его ис-
ходными размерами. Пятый параметр определяет коэффициент масштабирования.
Мы решили отобразить изображение в масштабе I: I и поэтому указали коэффициент
масштабирования равный 1, однако можно было бы выбрать и большее число с це-
лью увеличения изображения или дробное число для его уменьшения.
Наибольшую трудность при вставке в сертификат с использованием библиотеки
PDFlib представляет розетка. Мы не можем автоматически открыть и вставить в до-
кумент существующий метафайл Windows с изображением розетки, однако можем
нарисовать любые формы.
Чтобы нарисовать закрашенную форму, например ленточку, мы написали сле-
дующий код.
В приведенных ниже строках мы устанавливаем цвет штриха или линии черным, а
цвет заливки или внутренней части формы темно-синим:
pdf_setcolor($pdf, 'fill', 'rgb', 0, 0, .4, 0); // темно-синий
pdf_setcolor($pdf, 'stroke', 'rgb', 0, 0, 0, 0); II черный
Затем мы рисуем пятиугольник, который будет представлять одну из ленточек, и
выполняем его заливку:
pdf_moveto($pdf, 630, 150);
pdf_lineto($pdf, 610, 55);
pdf_lineto($pdf, 632, 69);
pdf_lineto($pdf, 646, 49);
pdfJLineto($pdf, 666, 150);
pdf_closepath($pdf);
pdf_fill($pdf);
Поскольку необходимо, чтобы многоугольник имел четкий контур, нужно еще раз
определить этот же путь, но на сей раз обратиться к функции pdf_stroke (), а не
pdf_fill().
792
Часть V. Реальные проекты на РНР и MySQL
Так как многолучевая звезда является сложной повторяющейся формой, мы под-
готовили функцию для вычисления точек вдоль пути. Эта функция имеет имя
draw_star () и требует передачи ей координат х и у центра, необходимого количества
лучей, длины лучей, идентификатора PDF-документа, а также булевского значения
для указания того, должна ли форма звезды быть залитой или просто контуром.
Функция draw_star () использует некоторые основные тригонометрические
формулы для вычисления расположения ряда лучей, образующих звезду. Для каждого
луча мы определяем точку на радиусе звезды и точку на меньшей окружности, распо-
ложенной на расстоянии $point_size внутри внешней окружности, после чего рису-
ем линию между ними. Следует отметить, что тригонометрические функции РНР на-
подобие cos () и sin () работают с углами, заданными в радианах, а не градусах.
Используя эту функцию и математические формулы, можно аккуратно сгенериро-
вать сложную повторяющуюся форму. Если бы для рамки страницы требовался слож-
ный узор, можно было бы воспользоваться аналогичным подходом.
После того как все элементы страницы сгенерированы, необходимо завершить
формирование страницы и документа.
Решение проблем, связанных с заголовками
Один незначительный нюанс во всех этих сценариях, который все же стоит отме-
тить, заключается в том, что браузеру потребуется указать, какой тип данных мы со-
бираемся ему отправлять. Это делается через HTTP-заголовок Content-Type, как по-
казано в следующих примерах:
header( 'Content-Type: application/msword' );
или
header! 'Content-Type: application/pdf1 );
Следует иметь в виду, что браузеры обрабатывают эти заголовки не особенно после-
довательно. В частности, Internet Explorer зачастую игнорирует MIME-тип и предприни-
мает попытку автоматического определения типа файла. (Похоже, что данную проблему
компании Microsoft таки удалось решить в последних версиях этого браузера, поэтому
если вы с ней сталкиваетесь, имеет смысл обновить версию этого браузера.)
Некоторые заголовки могут вступать в противоречие с заголовками управления
сеансом. Для решения этой проблемы существует несколько путей. Мы установили,
что использование GET-параметров вместо POST-параметров или параметров с пере-
менными сеанса позволяет избежать упомянутой проблемы.
Еще одно решение состоит в том, чтобы не использовать встроенные PDF-
документы, а взамен предлагать пользователю загружать их.
Проблем можно также избежать, если написать две слегка отличающихся версии
кода: одну для браузера Netscape, а другую — для Internet Explorer.
Расширение проекта
Добавление некоторых более реалистичных экзаменационных вопросов, очевид-
но, могло бы расширить этот проект, однако он действительно задумывался только
как пример, иллюстрирующий доставку документов через Web.
Глава 32. Генерация персонифицированных документов в PDF-формате
793
К числу персонифицированных документов, которые может потребоваться дос-
тавлять в онлайновом режиме, относятся юридические документы, частично запол-
ненные формы заказов либо заявлений, или формы, которые требуют государствен-
ные учреждения.
Дополнительные источники информации
Дополнительные сведения, касающиеся PDF-документов (равно как и FDF-
формата), можно найти на сайте компании Adobe по адресу:
http://www.adobe.com/
Что дальше
В следующей главе мы рассмотрим появившиеся в РНР5 новые возможности ис-
пользования XML, а также вопросы подключения к API-интерфейсу Web-служб
компании Amazon с помощью REST и SOAP.
794
Часть V. Реальные проекты на РНР и MySQL
33
Подключение к
Web-службам с помощью
XML и SOAP
За последние несколько лет язык XML (Extensible Markup Language — расширяе-
мый язык разметки) превратился в важное средство обмена данными. В этой
главе мы воспользуемся интерфейсом Web-служб компании Amazon для построения
покупательской тележки на своем Web-сайте, который будет эксплуатировать сайт
Amazon как машину баз данных. (Это приложение получило название Tahuayo (Таху-
айо), по названию одного из притоков Амазонки.) При этом будут задействованы два
различных метода: SOAP и REST. Метод REST известен еще и как XML поверх HTTP.
Для реализации этих двух методов мы прибегнем к услугам встроенной РНР-
библиотеки SimpleXML и внешней библиотеки NuSOAP.
В этой главе, помимо прочих, подробно рассматриваются следующие темы:
Основы XML и SOAP.
Использование XML для обмена дат 1ыми с сайтом Amazon.
Выполнение синтаксического разбора XML-кода с помощью РНР-библиотеки
SimpleXML.
Кэширование ответов.
Обмен данными с сайтом Amazon с помощью библиотеки NuSOAP.
Задача
В рамках этого проекта перед нами стоят две цели: первая — помочь читателям в
ознакомлении с XML и SOAP и способами их использования в коде РНР. Вторая цель
связана с применением этих технологий для обмена данными с внешним миром. В
качестве примера, который может оказаться полезным при создании собственных
Web-сайтов, мы выбрали программу Web-служб сайта Amazon (Amazon Web Services).
Уже давно компания Amazon предлагает вспомогательную программу, которую
можно использовать для рекламы товаров, поставляемых Amazon, на своем Web-
сайте. При этом пользователи получают возможность открывать ссылки на страницы
каждого из товаров, представленных на сайте Amazon. Если кто-либо из посетителей
щелкает на ссылке, а затем приобретает тот или иной товар, вы получаете небольшие
комиссионные.
Программа Web-служб позволяет использовать сайт Amazon не только в качестве
машины баз данных: на сайте Amazon можно выполнять поиск и отображать резуль-
таты на собственном сайте, или же непосредственно заполнять покупательскую те-
лежку пользователя содержимым тех элементов, которые он выбрал во время про-
смотра сайта. Другими словами, клиент использует ваш сайт до тех пор, пока не
наступит время оплачивать выбранные товары, что нужно делать через сайт Amazon.
Обмен данными между вашим сайтом и сайтом Amazon может выполняться двумя
способами. Первый из них предполагает использование XML поверх HTTP, который
называется также REST (Representational State Transfer — Передача репрезентативно-
го состояния). Например, если требуется выполнить поиск, используя этот метод,
следует отправить обычный HTTP-запрос необходимой информации, а сайт Amazon
ответит XML-документом, содержащим запрошенную информацию. Затем можно
выполнить синтаксический разбор этого XML-документа и отобразить результаты
поиска конечному пользователю с помощью выбранного вами интерфейса. Процесс
отправки и получения данных посредством протокола HTTP очень прост, но степень
сложности анализа результирующего документа зависит от сложности документа.
Второй способ обмена данными с сайтом Amazon — использование протокола
SOAP, являющегося одним из стандартных протоколов Web-служб. Название этого
протокола представляет собой аббревиатуру от Simple Object Access Protocol (Простой
протокол доступа к объектам), но оказалось, что протокол не столь уж прост, поэтому
название слегка вводит в заблуждение. Результирующий протокол по-прежнему назы-
вается SOAP, однако это название больше не считается аббревиатурой.
В ходе разработки этого проекта мы построим клиент SOAP, который сможет от-
правлять запросы и получать ответы от сервера SOAP в Amazon. Эти ответы содер-
жат ту же информацию, что и ответы, полученные методом XML поверх HTTP, но
при этом будет применен другой подход к извлечению данных — через библиотеку
NuSOAP.
Конечная цель разработки этого проекта — построение Web-сайта по продаже
книг, который использует сайт Amazon в качестве машины базы данных. Мы постро-
им две альтернативные версии: одну с использованием метода REST и вторую с при-
менением протокола SOAP.
Основы XML
Сначала имеет смысл потратить некоторое время на ознакомление с основами
XML и Web-службами, на тот случай, если вам не знакомы эти концепции.
Как уже отмечалось, XML — это Extensible Markup Language (Расширяемый язык раз-
метки). Со спецификацией языка можно ознакомиться на сайте компании W3W. На
этом сайте (http://www.w3.org/XML/) представлен большой объем информации по
XML.
Язык XML построен на основе языка SGML — Standard Generalized Markup Language
(Стандартный обобщенный язык разметки). Для тех читателей, которые уже знако-
мы с языком HTML (Hypertext Markup Language — язык разметки гипертекста) (если
это не так, то вы начали чтение книги явно не с того раздела), концепции XML не
представя т особой сложности.
796
Часть V. Реальные проекты на РНР и MySQL
XML — это текстовый формат представления документов, в основе которого ле-
жит использование дескрипторов. В качестве примера рассмотрим текст в листинге
33.1, который представляет собой XML-документ, посылаемый сайтом Amazon в от-
вет на запрос XML поверх HTTP.
Листинг 33.1. XML-документ, описывающий первое издание этой книги
<?xml version="l. О" encoding="UTF-8"?>
<ProductInfo xmlns :xsi=''http: //www.w3 .org/2001/XMLSchema-instance"
xsi :noNamespaceSchemaLocation="http: //xml .amazon. com/schemas2/ dev-heavy.xsd">
<Details
url="http://www.amazon.com/exec/obidos/redirect?tag=tangledwebdesign%
26creative=XXXXXXXXXXXXXX%26camp=2025%261i
nk_code=xm2%26path=ASIN/0672317842">
<Asin>0672317842</Asin>
<ProductName>PHP and MySQL Web Development</ProductName>
<Catalog>Book</Catalog>
<Authors>
<Author>Luke Welling</Author>
<Author>Laura Thomson</Author>
</Authors>
<ReleaseDate>30 March, 2001</ReleaseDate>
<Manu fac turer> Sams </Manuf ac turer>
<ImageUrlSmall>http://images.amazon.com/images/Р/0672317842.01.
THUMBZ ZZ.j pg</ImageUr1Sma11>
<ImageUrlMedium>http://images.amazon.com/images/Р/0672317842.01.
MZZZZZZZ.jpg</ImageUrlMedium>
<ImageUrlLarge>http://images.amazon.com/images/P/0672317842.01.
LZZZZZZZ.jpg</ImageUrlLarge>
<ListPrice>$49.99</ListPrice>
<OurPrice>$34.99</OurPrice>
<UsedPrice>$31.95</UsedPrice>
<ThirdPartyNewPrice>$31.75</ThirdPartyNewPrice>
<SalesRank>312</SalesRank>
<Lists>
<ListId>3KZWlEV9QMB5F</ListId>
<ListId>22YC01IGPIZJ3</ListId>
<ListId>Y2I9B362QXVX</ListId>
</Lists>
<BrowseList>
<BrowseNode>
<BrowseName>PHP (Computer program language</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>SQL (Computer program language</BrowseName>
</Brows eNode>
<BrowseNode>
<BrowseName>Web sites</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Design</BrowseName>
</BrowseNode>
<BrowseNode>
Глава 33. Подключение к Web-службам с помощью XML и SOAP
797
<BrowseName>SQL (Computer language)</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Sql (Programming Language)</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Computer Networks</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Computer Bks - Languages / Programming</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Computers</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Programming Languages — Generate/BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Internet - Web Site Designe/BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Database Management - SQL Server</BrowseName>
</BrowseNode>
<BrowseNode>
<BrowseName>Programming Languages - SQL</BrowseName>
</BrowseNode>
</BrowseList>
<Media>Paperback</Media>
<NumMedia>l</NumMedia>
<Isbn>0672317842</Isbn>
<Availability>Usually ships within 24 bourse/Availability>
eSimilarProducts>
eProduct>0735709211e/Product>
eProduct>186100373Oe/Product>
e Produc t>073570970Xe/Product>
eProduct>1861006918e/Product>
eProduct>0596000413e/Product>
e/SimilarProducts>
e/Details>
e -'ProductInfo
Документ начинается co следующей строки:
e?xml version="l. 0" encoding="UTF-8"?>
Это стандартное объявление сообщает, что следующий документ будет XML-
документом, в котором используется кодировка символов UTF-8.
Теперь рассмотрим тело документа. Весь документ состоит из пар открывающих и
закрывающих дескрипторов наподобие:
eProductName>PHP and MySQL Web Developmente/ProductName>
ProductName — это элемент, аналогичный элементам HTML. Точно так же, как в
HTML, элементы могут быть вложенными один в другой:
798
Часть V. Реальные проекты на РНР и MySQL
<Authors>
<Author>Luke Welling</Author>
<Author>Laura Thomson</Author>
</Authors>
Так же, как в HTML, элементы могут иметь атрибуты, как показано в следующем
примере:
«Details url="http://www.amazon.com/exec/obidos/redirect?tag=
tbtangledwebdesign%26creative=XXXXXXXXXXXXXX%26camp=2025%261ink_code=
4>xm.2%26path=ASIN/0672317842 ">
Этот элемент Details имеет единственный атрибут: url. Поскольку' URL-адрес
очень длинный, в этом примере он представлен в нескольких строках.
Имеется также ряд отличий от языка HTML. Во-первых, для каждого открываю-
щего дескриптора должен существовать соответствующий закрывающий дескриптор.
Единственное исключение из этого правила — пустые элементы, которые открыва-
ются и закрываются в одном дескрипторе, поскольку они не содержат никакого тек-
ста. Если вы знакомы с языком XHTML, то встречали дескриптор <Ьг />, который
используется вместо дескриптора <Ьг> именно по этой причине. Кроме того, все
элементы должны быть корректно вложены. Вероятно, используя анализатор HTML,
можно получить нужный результат анализа строки <bxi>TeKCT</bx/i>, но чтобы
быть допустимыми дескрипторами XML или XHTML, они должны быть вложены со-
ответствующим образом, как, например, <bxi>TeKcT</ix/b>.
Основное заметное различие между языками XML и HTML заключается в том, что
в ходе создания документа можно создавать собственные дескрипторы! Это обуслов-
лено гибкостью XML. Документы можно структурировать в соответствии с данными,
которые нужно в них хранить. Структуру XML-документов можно формализовать,
создавая либо определение типа документа (Document Type Definition — DTD) либо
схему XML (XML Schema). Оба эти документа служат для описания структуры данного
XML-документа. При желании DTD или схему' можно считать своего рода объявлени-
ем класса, а XML-документ — экземпляром этого класса. В приведенном примере DDT
или схема не используются.
Прочитать DTD этого документа от Amazon можно по следующему адресу:
http://xml.amazon.сот/schemas2/dev-heavy.dtd
Его схема XML доступна здесь:
http://xml.amazon.com/schemas2/dev-heavy.xsd
DTD-файл нельзя открыть в некоторых браузерах, поскольку они будут считать
DTD-документ XML-документом и могут дать сбой. Однако его можно загрузить и
прочесть в любом текстовом редакторе. Файл схемы XML должен быть доступен для
открытия непосредственно в браузере.
Обратите внимание, что за исключением начального XML-объявления все тело
документа содержится внутри элемента ProductInfo. Этот элемент называется корне-
вым элементом документа. Рассмотрим его подробнее:
«ProductInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://xml.amazon.com/schemas2/
dev-heavy.xsd">
Глава 33. Подключение к Web-службам с помощью XML и SOAP
799
Он содержит несколько необычных атрибутов. Это iipocmpaHcmea имен XML (XML
namespaces). Для разработки этого проекта понимание пространств имен не обяза-
тельно, но они могут быть весьма полезными. Основная идея состоит в определении
имен элементов и атрибутов с помощью пространства имен, чтобы обычно используе-
мые имена не конфликтовали при работе с документами из различных источников.
Для получения дополнительной информации о пространствах имен рекомендуется
прочесть документ "Namespaces in XML Recommendation” ("Пространства имен в ре-
комендациях noXML”), доступный по адресу http: / /www.w3 .org/TR/REC-xml-names/.
Более подробную информацию по языку XML в целом можно получить из множе-
ства источников. Прекрасная отправная точка — сайт компании W3C. Кроме того,
существуют сотни хороших книг и интерактивных учебников. Сайт ZVON.org — один
из лучших источников такой информации, доступных в Web.
Основы Web-служб
Weli-службы — это интерфейсы приложений, доступные через World Wide Web.
Если вы предпочитаете мыслить категориями объектно-ориентированного програм-
мирования, Web-службу можно считать классом, который предоставляет свои об-
щедоступные методы посредством Web. В настоящее время Web-службы получили
широкое распространение, и некоторые из крупнейших компаний делают функ-
циональные возможности своих приложений доступными с помощью механизма
Web-служб.
Например, Google, Amazon, eBay и PayPal предлагают набор Web-служб. После то-
го, как в ходе ознакомления с материалом этой главы вы настроите клиент интер-
фейса Amazon, создание клиентского интерфейса для сайта Google должно быть дос-
таточно простой задачей. Более подробную информацию можно найти на Web-сайте
http://www.google.com/apis/.
Постоянно расширяющийся список общедоступных Web-служб представлен на
сайте http: //www.xmethods.net.
Эта методология удаленного вызова функций предполагает использование не-
скольких протоколов. Двумя наиболее важными из них являются SOAP и WSDL.
SOAP
SOAP — это управляемый запросами и ответами протокол обмена сообщениями,
который позволяет клиентам вызывать Web-службы, а серверам отвечать им. Каждое
сообщение SOAP, будь то запрос или ответ, является простым XML-документом.
Пример запроса SOAP, который можно отправить сайту Amazon, показан в листинге
33.2.
Листинг 33.2. Запрос SOAP на выполнение поиска по ASIN
<?xml version="l. О" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns: SOAP-ENV="http: / /schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns: xsi="http: / /www. w3 . org/2001/XMLSchema-instance”
xmlns :xsd="http: //www.w3 .org/2001/XMLSchema"
SOAP-ENV: encodingStyle=''http: //schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV: Body>
800
Часть V. Реальные проекты на РНР и MySQL
<namespl:AsinSearchRequest xmlns :namespl="urn: PI/DevCentral/SoapService">
<AsinSearchRequest xsi:type="m:AsinRequest">
<asin >0060518057</asin>
<tag >your-associate-id</tag>
<type >heavy</type>
<dev-tag >your-dev-tag</dev-tag>
</AsinSearchRequest>
<'namespl:AsinSearchRequest>
< SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Сообщение SOAP начинается с объявления того, что данный документ является
XML-документом. Корневым элементом всех сообщений SOAP является конверт
SOAP. Внутри него находится элемент Body, содержащий собственно запрос.
Запросом служит элемент AsinSearchRequest, который просит сервер Amazon
выполнить в своей базе данных поиск конкретного элемента по ASIN — Amazon.com
Standard Item Number (Стандартный номер элемента Amazon.com). Это — уникаль-
ный идентификационный номер, присвоенный каждому товару в базе данных Ama-
zon.com.
Элемент AsinSearchRequest можно считать вызовом функции на удаленном ком-
пьютере, а содержащиеся внутри него элементы — параметрами, передаваемыми этой
функции. В данном случае мы передаем ASIN книги Way of the Weasel, написанной Дил-
бертом (Dilbert). Серверу необходимо передать также дескриптор, представляющий
конкретный Associate ID (Дополнительный идентификатор), тип поиска, который
нужно выполнить (heavy или lite), и dev-tag, представляющий собой метку разра-
ботчика, которую присвоит вам сервер Amazon. Элемент типа сообщает службе, тре-
буются ли ограниченные сведения (lite) или вся доступная информация (heavy).
Ответ на этот запрос подобен XML-документу, представленному в листинге 33.1,
но он помещен в конверт SOAP.
Как правило, при работе с протоколом SOAP, генерирование запросов SOAP и
интерпретация ответов выполняется программно с использованием библиотеки
SOAP, независимо от применяемого языка программирования. Это очень удобно,
поскольку избавляет от необходимости строить запросы SOAP и интерпретировать
ответы вручную.
WSDL
WSDL представляет собой аббревиатуру от Web Services Description Language (Язык
описания Web-служб). Этот язык применяется для описания интерфейса доступ-
ных служб на конкретном Web-сайте. WSDL-документ, описывающий Web-службу
Amazon, используемую в данной главе, можно найти на Web-странице по адресу
http://soap.amazon.com/schemas2/AmazonWebServices.wsdl.
Последовав по этой ссылке, можно будет убедиться, что WSDL-документы значи-
тельно сложнее сообщений SOAP. По возможности их всегда следует генерировать и
интерпретировать программно.
Более подробную информацию по WSDL можно найти на сайте проектов W3C
Draft http: //www.w3 . org/TR/wsdl20/.
Глава 33. Подключение к Web-службам с помощью XML и SOAP
801
На момент написания книги язык WSDL еще не был рекомендацией консорциума
W3C, поэтому в него продолжают вноситься изменения. Тем не менее, это обстоя-
тельство не мешает разработчикам с большим энтузиазмом его применять. Однако
повторимся еще раз, что подобно всем фрагментам мозаики Web-служб, этот язык
продолжает претерпевать изменения, поскольку область в целом быстро развивается.
Компоненты решения
Для решения задачи требуется разработка нескольких компонентов. Кроме наи-
более очевидных компонентов — интерфейса покупательской тележки, предназна-
ченного для отображения клиентов, и кода подключения к Web-сллжбе Amazon через
протокол REST или SOAP — необходимы дополнительные компоненты. После того
как XML-документ получен, код должен выполнить его анализ с целью извлечения
информации, которая будет отображаться интерфейсом тележки. Для удовлетворе-
ния требований, предъявляемых компанией Amazon, и для повышения производи-
тельности следует подумать о применении кэширования. И, наконец, поскольку
действия по оплате заказа должны выполняться на сайте Amazon, требуются опре-
деленные функциональные компоненты для передачи содержимого тележки пользо-
вателя и самого пользователя Web-службе Amazon.
Построение покупательской тележки
Очевидно, что интерфейсом системы должно быть приложение покупательской
тележки. Это уже было сделано в главе 27. Поскольку покупательские тележки не яв-
ляются основной целью данного ^проекта, в этой главе мы используем упрощенное
приложение. Нам потребуется создать лишь упрощенную версию тележки, позво-
ляющую отслеживать товары, которые клиент хотел бы приобрести, и сообщать о
них сайту Amazon до наступления момента оплаты заказа.
Использование интерфейсов Web-служб Amazon
Чтобы использовать интерфейс Web-служб Amazon, необходимо загрузить ком-
плект разработчика Web-служб Amazon Web Services Developers' Kit. Мы получили его
с Web-странипы http://www.amazon.eom/gp/aws/landing.htir.l. Тем не менее, упо-
мянутый URL-адрес может измениться.
Необходимо также подписаться на пароль разработчика. Это делается на том же
сайте. Данный пароль используется для идентификации на сайте Amazon при запросе
разрешения на подключение.
Можно также подписаться на идентификатор компаньона Amazon Associate ID.
Этот идентификатор позволяет получать комиссионные, если кто-либо приобретает
товары через созданный вами интерфейс.
После загрузки комплекта разработчика просмотрите его. Он поставляется вместе
с документацией, описывающей работу интерфейса, и примерами кода на различных
языках программирования, в том числе и на РНР.
Прежде чем можно будет загрузить этот комплект, нужно принять лицензионное
соглашение. Его стоит внимательно прочесть, поскольку оно отличается от обычных
802
Часть V. Реальные проекты на РНР и MySQL
лицензионных соглашений по использованию программного обеспечения. Оно со-
держит ряд условий, соблюдение которых важно при реализации приложения:
Вы не должны выдавать более одного запроса в секунду.
Вы должны кэшировать данные, поступающие с сайта Amazon.
Большинство данных можно помещать в кэш на период до 24 часов, а некоторые
стабильные атрибуты — на период до трех месяцев.
При помещении в кэш информации о ценах и доступности на период более од-
ного часа вы должны выводить предупреждение.
Все ссылки должны адресоваться обратно на сайт Amazon.com, а текстовые и
графические элементы, загруженные с сайта Amazon, не должны снабжаться
ссылками на другие коммерческие сайты.
Учитывая трудное произношение имени домена, отсутствие рекламы и очевидных
причин использования сайта Tahuayo.com вместо обращения непосредственно к сай-
ту Amazon.com, поддержание частоты запросов меньшей одного запроса в секунд}’ не
требует никаких дополнительных действий.
В этом проекте мы реализуем кэширование, удовлетворяющее условиям пунктов
2—4. Приложение будет кэшировать изображения на период в 24 часа, а сведения о
товарах (сведения о цене и доступности) — на 1 час.
Разбор XML-документа
Первый интерфейс Web-служб, предлагаемый компанией, реализован посредст-
вом протокола REST. Этот интерфейс принимает обычный HTTP-запрос и возвраща-
ет XML-документ. Чтобы использовать этот интерфейс, необходимо иметь возмож-
ность выполнять разбор XML-ответа, возвращаемого сайтом Amazon. Это можно
делать с помощью PHP-библиотеки SimpleXML. Эта библиотека требует наличия РНР
версии не ниже 5.0.0, и она включена по умолчанию.
Использование SOAP с РНР
Другой интерфейс, предоставляющий доступ к тем же Web-службам — SOAP. Для
получения доступа к этим службам через SOAP необходимо воспользоваться одной из
разнообразных библиотек SOAP языка РНР. Существует встроенная библиотека
SOAP, но поскольку она доступна не всегда, можно применять библиотеку NuSOAP.
Так как библиотека NuSOAP написана на языке РНР, она не требует компиляции.
Она представляет собой единственный файл, который нужно вызвать с помощью
require_once().
Библиотека NuSOAP доступна по адресу http://dietrich.ganx4.com/nusoap/.
Правила ее использования регламентируются лицензионным соглашением Lesser
GPL; то есть ее можно использовать в любых приложениях, в том числе и платных.
Кэширование
Как уже упоминалось, в соответствии с условиями, предъявляемыми компанией
Amazon к разработчикам, данные, загруженные из сайта Amazon через Web-службы,
должны кэшироваться. В разрабатываемом решении нам придется найти способ хра-
Глава 33. Подключение к Web-службам с помощью XML и SOAP
803
нения и многократного использования загруженных данных до момента истечения
соответствующего срока.
Обзор решения
В этом проекте мы снова воспользуемся подходом, управляемым событиями, ко-
торый применялся в главах 29 и 30. В этом примере мы не приводим блок-схему сис-
темы, поскольку приложение содержит всего несколько экранов, а связи между ними
просты.
Пользователи будут начинать работу с главного экрана сайта Tahuayo. показанно-
го на рис. 33.1.
Рис. 33.1. Первый экран сайта Tahuayo отображает все ос-
новные функциональные элементы сайта: средства навигации
по категориям, средства поиска и покупательскую тележку
Как видите, основные элементы сайта — раздел избранных категорий (Selected
Categories) и элементы этих категорий. По умолчанию на титульной странице ото-
бражаются бестселлеры категории документальных книг. Если пользователь щелкнет
на другой категории, отобразится аналогичная титульная страница соответствующей
категории.
Прежде чем продолжить, приведем краткое описание используемых терминов:
компания Amazon называет категории узлами просмотра (browse nodes). Это выраже-
ние будет встречаться как в коде, так и в официальной документации.
В документации приведен сокращенный список популярных узлов просмотра.
Кроме того, если необходимо увидеть конкретный узел просмотра, можно выполнить
просмотр на обычном сайте Amazon.com и прочитать узел из URL-адреса. Однако не
существует способа ознакомления с полным списком узлов. К сожалению, некоторые
важные категории, такие как наиболее продаваемые книги, не доступны в качестве
узлов просмотра.
804
Часть V. Реальные проекты на РНР и MySQL
В нижней части этой страницы представлена информация о других книгах и
ссылки на дополнительные страницы (они не поместились на снимке экрана). На
каждой странице будет отображаться информация о 10 книгах и до 30 ссылок на дру-
гие страницы. Это значение, равное 10 книгам на страницу, было установлено сайтом
Amazon. 30-страничный предел был выбран нами произвольно.
С этой страницы пользователи могут получать подробную информацию об от-
дельных книгах, щелкая на соответствующих ссылках. Соответствующий экран пока-
зан на рис. 33.2.
Рис. 33.2. Страница подробных сведений содержит до-
полнительную информацию о конкретной книге, в том
числе информацию об аналогичных товарах и рецензии
Хотя на снимке поместилась и не вся информация, которую сайт Amazon отправ-
ляет в ответ на подробный запрос этой страницы, на нем все же видна большая часть
сведений. Мы решили пренебречь теми разделами страницы, которые посвящены не
книгам.
Щелкнув на изображении обложки, пользователь получает возможность просмот-
реть увеличенную версию изображения.
В верхней части экрана, показанной на этих рисунках, видно поле поиска. Этот
элемент позволяет выполнять поиск по ключевому слову на данном сайте и в каталоге
сайта Amazon через его интерфейс Web-служб. Пример результата выполнения поис-
ка можно видеть на рис. 33.3.
Хотя в этом проекте перечислены только несколько категорий, клиенты могут об-
ратиться к любой книге, используя функции поиска и переходя к конкретным книгам.
Каждая отдельная книга имеет связанную с ней ссылку Add to Cart (Добавить в тележ-
ку). Щелчок на этой ссылке или на ссылке Details (Сведения) на итоговой странице те-
Глава 33. Подключение к Web-службам с помощью XML и SOAP
805
лежки перемещает пользователя на страницу содержимого тележки. Эта страница по-
казана на рис. 33.4.
TjhiKryO.com - An Amazonian Tr ifujt jr у
i-ohlp- Лиа,*»эй <h3K^3^'k.d5'\pnp?acf:“<=sec'C'<Ssea с-- =
tahuayo.com
SeJecied Categories
Search Result* Г-о« ’заштан'
Рис. 33.3. Этот экран отображает результаты поиска
по ключевому слову aardman
Е» Edit
i'-a-tsa:
tahuayo.com.
Уош Shuppht*
When -.'ou nave finishes shopping press pheckotf
cart to ^cu! Amazon an-J ccrc-pi-te vgut tx-i
e tir-isl-
=ill items
; seats n;
creating
’J3-D.
^дпаноп
Is- Petei и
'list pri2«
C.-uantit Г Сач
rctal Prlce
:ssn о?1с-?:чч-
."„stcme. Rating
Рис. 33.4. На странице покупательской тележки клиент
может удалить товары, очистить тележку или перейти к
оплате заказа
oei-33100^
806
Часть V. Реальные проекты на РНР и MySQL
И, наконец, когда клиент выполняет оплату заказа, щелкая на одной из ссылок
Checkout (Оплата) сведения о содержимом его покупательской тележки отправляют-
ся на сайт Amazon, и пользователь направляется на него. В браузере клиента откры-
вается страница, аналогичная показанной на рис. 33.5.
14 amazon.com
•L'z | НЕ1Г
i. ME MOSE
- 1rests
Shopping Cart Items—To Buy Now
i. Hardcover
quakes
ю select FREE Super Saver Shipping as ye
Рис. 33.5. Товары, которые были помещены в покупа-
тельскую тележку клиента на сайте Tahuayo, теперь нахо-
дятся в его тележке на сайте Amazon
Маке sj*e
RECEHTIV VIEWED ITEMS
____________________ordering.
Your order qualifies for free shipping! (Sy<_
Make sure to select
shipping speed at checkout.
Теперь вы должны уяснить, что понимается под созданием собственного интер-
фейса и использованием сайта Amazon в качестве машины базы данных.
Поскольку в этом проекте мы снова используем подход, управляемый событиями,
большая часть логики принятия решений в ходе выполнения приложения размещает-
ся в единственном файле, index.php. Перечень файлов приложения представлен в
табл. 33.1.
Для функционирования приложения требуется также упоминавшийся ранее файл
nusoap.php, поскольку он необходим для работы перечисленных файлов. Файл
NuSOAP можно найти в каталоге chapters 3 на прилагаемом к этой книге компакт-
диске, но поскольку автор продолжает поддерживать эту библиотеку, возможно, име-
ет смысл загрузить обновленную версию с Web-страницы:
http://dietrich.ganx4.com/nusoap/index.php
Начнем рассмотрение этого проекта с файла ядра приложения index. php.
Глава 33. Подключение к Web-службам с помощью XML и SOAP
807
Таблица 33.1. Файлы приложения Tahuayo
Имя файла Tun Описание
index.php Приложение Содержит главный файл приложения.
about.php Приложение Отображает страницу About (О приложении).
constants.php Включаемый файл Определяет глобальные константы и переменные.
topbar.php Включаемый файл Генерирует информационную строку, отобра- жаемую вдоль верхнего края каждой страницы, и вложенную таблицу стилей.
bottom.php Включаемый файл Генерирует нижний колонтитул, отображаемый в нижней части каждой страницы.
AmazonResultSet.php Файл класса Содержит класс РНР, хранящий результаты каж- дого из запросов к сайту Amazon.
Product.php Файл класса Содержит класс РНР, хранящий информацию об одной конкретной книге.
bookdisplayfunctions.p Функции Содержит функции, которые помогают отобра- жать книги и списки книг.
cachefunctions.php Функции Содержит функции для выполнения кэширова- ния. требуемого сайтом Amazon.
cartfunctions.php Функции Содержит функции, связанные с покупательской тележкой.
categoryfunctions.php Функции Содержит функции, которые помогают извлекать и отображать категории.
utilityfunctions.php Функции Содержит несколько вспомогательных функций, используемых в приложении.
Ядро приложения
Содержимое файла приложения index .php показано в листинге 33.3.
Листинг 33.3. index.php — файл ядра приложения
<?php
// Для хранения содержимого тележки мы используем только одну
// переменную сеанса 'cart'
session—start();
require—once('constants.php1);
require—once('Product.php');
require_once('AmazonResultSet.php');
require—once('utilityfunctions.php');
require_once('bookdisplayfunctions.php');
require_once('cartfunctions.php');
require—once('categoryfunctions.php');
// Эти переменные должны поступать извне.
// Код будет проверять их допустимость и преобразовывать
// в глобальные переменные
$external = array('action', 'ASIN', 'mode', 'browseNode', 'page1, 'search');
// Переменные могут поступать через метод GET или POST.
// Все ожидаемые внешние переменные преобразуются в короткие глобальные имена
808
Часть V. Реальные проекты на РНР и MySQL
foreach (fexternal as fe)
{
if(@$_REQUEST[$e])
ffe = f_REQUEST[fe];
else
$$e = " ;
ffe = trim(ffe);
}
// Значения глобальных переменных, определенные по умолчанию
if(fmode=='')
fmode = 'books'; // Никакие другие режимы не тестировались
if($browseNode=='')
$browseNode = 53; //53 — это документальный бестселлер
if($page=='')
fpage =1; // Первая страница — каждая страница содержит 10 наименований
// Проверка/усечение ввода
if(!eregi('~[A-ZO-9]+f', fASIN)) // идентификаторы ASIN должны быть
// алфавитно-цифровыми
fASIN ='';
if (! eregi ( '74 [a-z] +$ ' , fmode)) // режим должен быть алфавитным
$mode = 'books';
$page=intval(fpage); // значения переменных page и browseNodes должны быть
// целочисленными
$browseNode = intval(fbrowseNode);
// Это может вызывать определенное недоумение, но мы отбрасываем некоторые
// символы из значения $search, поскольку нам представляется уместным
// изменить его на данном этапе, так как оно будет отображаться в заголовке
$search = safeString(fsearch);
i f(!isset($_SESSION['cart']))
{
session_register('cart');
$_SESSION['cart'] = array!);
}
// Задачи, которые должны быть выполнены до отображения верхней строки
if(faction == 'addtocart')
addToCart(f_SESSION['cart'], $ASIN, fmode);
if(faction == 'deletefromcart')
deleteFromCart(f_SESSION['cart'], fASIN);
if(faction == 'emptycart')
f.SESSION[’cart'] = array();
// Отображение верхней строки
require_once ('topbar.php');
//Главный цикл событий. Реагирует на действия пользователя на вызывающей странице
switch (faction)
{
case 'detail' :
showcategories(fmode);
showDetail(fASIN, fmode);
break;
Глава 33. Подключение к Web-службам с помощью XML и SOAP
809
case 'addtocart' :
case 'deletefromcart' :
case 'emptycart' :
case 'showcart1 :
echo '<hr /><hl>Your Shopping Cart</hl>';
showCart($_SESSION['cart’], Smode);
break;
case 'image' :
showcategories(Smode);
echo '<hl>Large Product Image</hl>';
showimage($ASIN, $mode);
break;
case 'search' :
showcategories($mode);
echo "<hl>Search Results For '$search'</hl>";
showsearch($search, $page, Smode);
break;
case 'browsenode':
default:
showcategories($mode);
Scategory = getCategoryName(SbrowseNode);
if(!Scategory||$category=='Best Selling Books')
{
echo '<hl>Current Best Sellers</hl>';
}
else
{
echo ''<hl>Current Best Sellers in $category</hl>” ;
}
showBrowseNode($browseNode, Spage, Smode) ;
break;
}
require ('bottom.php');
Давайте проанализируем этот файл. Он начинается с создания сеанса. Подобно
тому, как это выполнялось ранее, покупательская тележка клиента сохраняется в виде
переменной сеанса.
Затем мы включаем ряд файлов. Большинство из них — библиотеки функций, о
которых будет рассказано далее, а теперь мы рассмотрим первый включаемый файл.
Этот файл, constants.php, определяет набор важных констант и переменных, кото-
рые будут использоваться в приложении. Содержимое файла constants .php приве-
дено в листинге 33.4.
Листинг 33.4. constants .php — объявление основных глобальных констант и
переменных
<?php
// Это приложение может подключаться с помощью метода REST (XML поверх HTTP)
// или SOAP.
// Выберите версию метода (METHOD).
810
Часть V. Реальные проекты на РНР и MySQL
// define!'METHOD', ’SOAP’);
define!'METHOD', 'REST');
// Обязательно создайте каталог кэша и сделайте его доступным для записи
define('CACHE', 'cache'); // путь к кэшированным файлам
define('ASSOCIATEID', 'webservices-20'); // поместите здесь свой Associate ID
define('DEVTAG', 'XXXXXXXXXXXXXX'); // поместите здесь свою метку разработчика
// Вывод сообщения об ошибке, если программа выполняется с фиктивным devtag
if(DEVTAG=='XXXXXXXXXXXXXX')
{
die ('You need to sign up for an Amazon.com developer tag at<a href =
"https: //associates.amazon.corn/exec/panama/associates/join/
^developer/application.html/ref=sc_bb_l_0/002">Amazon</a>
when you install this software. You should probably sign up
for an associate ID at the same time. Edit the file constants.php.');
}
// (частичный) список узлов просмотра сайта Amazon.
$categoryList = array(5=>'Computers & Internet', 3510=>'Web Development',
295223=>'PHP', 17=>'Literature and Fiction',
3=>'Business & Investing', 53=>'Non Fiction',
23=>'Romance', 75=>’Science', 21=>'Reference',
6 =>'Food & Wine', 27=>'Travel',
16272=>'Science Fiction'
) ;
Это приложение может использовать либо метод REST, либо SOAP. Метод, кото-
рый должно использовать приложение, можно указывать, изменяя значение кон-
станты METHOD.
Константа CACHE содержит путь к кэшу данных, загруженных с сайта Amazon. Из-
мените ее значение на конкретный путь в своей системе.
Константа ASSOCIATEID содержит значение идентификатора Associate ID. При от-
правке этого значения на сайт Amazon вместе с транзакциями вы будете получать ко-
миссионные. Не забудьте его изменить на значение своего идентификатора Associate
ID.
Константа DEVTAG содержит значение метки разработчика, присвоенной сайтом
Amazon при регистрации. Необходимо изменить это значение на значение своей
метки разработчика; в прот ивном случае приложение работать не будет. Подписка на
упомянутую метку осуществляется по следующему адресу:
http://www.amazon.сот/др/aws/landing.html
Вернемся снова к файлу index.php. Он содержит ряд предварительных операто-
ров и главный цикл событий. Процесс начинается с извлечения любых входных пе-
ременных из суперглобальной переменной $_REQUEST, поступающей с использовани-
ем методов GET или POST. Затем устанавливаются используемые по умолчанию
значения для ряда стандартных глобальных переменных, определяющих данные, ко-
торые будут отображаться впоследствии, как показано ниже:
Глава 33. Подключение к Web-службам с помощью XML и SOAP
811
// Значения глобальных переменных, определенные по умолчанию
if($mode=='')
$mode = 'books'; // Никакие другие режимы не тестировались
i f($browseNode==1')
$browseNode = 53; //53 — это документальный бестселлер
if($page=='')
$page = 1; // Первая страница - каждая страница содержит 10 наименований
В качестве значения переменной $mode мы устанавливаем books. Сайт Amazon
поддерживает множество других режимов (типов товаров), но в данном приложении
нас интересуют только книги. Изменение предложенного в этой главе кода для рабо-
ты с другими категориями не должно представлять особой сложности. Первым шагом
такого расширения области приложения, была бы переустановка значения $mode.
При этом потребовалось бы выяснить в документации значения атрибутов, возвра-
щаемые для товаров, отличных от книг, и удалить из интерфейса пользователя все
элементы, относящиеся к книгам.
Переменная $browseNode определяет категорию книг, которая должна отобра-
жаться на странице. Эта переменная может устанавливаться, если пользователь щел-
кает на одной из ссылок Selected Categories (Избранные категории). Если она не ус-
тановлена — например, когда пользователь впервые заходит на сайт — ее значение
будет равно 53. Узлы просмотра сайта Amazon — это всего лишь целые числа, которые
идентифицируют категорию. Значение равное 53 представляет категорию докумен-
тальных книг. С учетом того, что ряд наиболее популярных общих категорий не дос-
тупен в качестве узлов просмотра, этот узел подходит для отображения на начальной
титульной странице ничуть не меньше любого другого.
Переменная $раде сообщает сайту Amazon, какой поднабор результатов жела-
тельно отображать внутри данной категории. Страница 1 содержит результаты 1—10,
страница 2 — результаты 11—20 и так далее. Сайт Amazon устанавливает количество
элементов, отображаемых на странице, поэтому управлять этим значением не нужно.
Конечно, на одной своей странице можно было отображать две или более "страниц”
данных сайта Amazon, но 10 — выбор, который представляется наиболее рациональ-
ным и не требует дополнительных действий.
Теперь необходимо привести в порядок любые входные данные, полученные как
через поле поиска, так и посредством методов GET или POST:
// Проверка/усечение ввода
if(1 eregi('Л[A-Z0-9]+$1, $ASIN)) // идентификаторы ASIN должны быть
// алфавитно-цифровыми
$ASIN ='';
if(1eregi('Л[a-z]+$', Smode)) /7 режим должен быть алфавитным
$mode = 'books';
$page=intval($page); // значения переменных page и browseNodes должны быть
// целочисленными
$browseNode = intval($browseNode);
// Это может вызывать определенное недоумение, но мы отбрасываем некоторые
// символы из значения $search, поскольку нам представляется уместным
// изменить его на данном этапе, так как оно будет отображаться в заголовке
$search = safeString($search);
812
Часть V. Реальные проекты на РНР и MySQL
Здесь нет ничего нового. Функция safestring () находится в файле utilityfunc-
tions .php. С помощью ршулярного выражения она просто удаляет из входной стро-
ки любые символы, отличные от алфавитно-цифровых. Поскольку эта тема была ос-
вещена ранее, мы не будем останавливаться на ней в этой главе.
Основная причина проверки на допустимость ввода связана с использованием
вводимой пользователем информации для формирования имен файлов в кэше. Если
разрешить клиентам вводить такие символы, как . . или /, это может привести к
серьезным проблемам.
Теперь необходимо создать покупательскую тележку клиента, если он еще ее не
имеет:
if(!isset($_SESSION['cart']))
{
session_register('cart');
$_SESSION['cart’] = array();
}
Прежде чем можно будет отобразить информацию в верхней информационной
строке страницы (см. рис. 33.1), нужно выполнить еще несколько задач. Эскиз поку-
пательской тележки отображается в верхней строке каждой страницы. Поэтому важ-
но, чтобы значение переменной тележки было актуальным на момент вывода этой
информации:
// Задачи, которые должны быть выполнены до отображения верхней строки
if($accion == 'addtocart')
addToCart($_SESSION[’cart'], $ASIN, Smode);
if(Section == 'deletefromcart')
deleteFromCart($_SESSION['cart'], $ASIN);
if(Section == 'emptycart')
$_SESSION['cart'] = array();
В приведенном фрагменте кода мы по мере необходимости добавляем или удаляем
элементы из тележки перед ее отображением. Мы вернемся к этим функциям при
рассмотрении покупательской тележки и оплаты заказа. Если же вы желаете ознако-
миться с ними немедленно, то эти функции определены в файле cartfunctions .php.
Пока стоит отложить их анализ, поскольку вначале необходимо разобраться в работе
интерфейса сайта Amazon.
Затем мы включаем файл topbar.php. Этот файл содержит лишь HTML-код, таб-
лицу стилей и единственный вызов функции ShowSmallCart () (которая определена в
файле cartfunctions.php). Сценарий topbar.php отображает краткое содержимое
тележки в верхнем правом углу страниц. Мы вернемся к нем}' при рассмотрении
функции покупательской тележки.
Наконец, мы подошли к рассмотрению главного цикла обработки событий. Крат-
кое описание возможных действий представлено в табл. 33.2.
Как видите, первые четыре действия, приведенные в этой таблице, связаны с по-
лучением информации с сайта Amazon и ее отображением. Следующие четыре дейст-
вия связаны с управлением содержимым покупательской тележки.
Все действия, связанные с получением данных с сайта Amazon, выполняются ана-
логично. В качестве примера рассмотрим получение сведений о книгах конкретного
узла просмотра (категории).
Глава 33. Подключение к Web-службам с помощью XML и SOAP
813
Таблица 33.2. Возможные действия главного цикла обработки событий
Действие Описание
browsenode Отображает книги укачанной категории. Это действие выполняется по умолчанию.
detail Отображает сведения об одной конкретной книге.
image Отображает увеличенную версию изображения обложки книги.
search Отображает результаты поиска, выполненного пользователем.
addtocart Добавляет элемент в покупательскую тележку.
deletefromcart Удаляет элемент из покупательской тележки.
emptycart Полностью опустошает покупательскую тележку.
showcart Отображает содержимое тележки.
Отображение книг конкретной категории
При активизации действия browsenode (просмотр категории) выполняется сле-
дующий код:
showcategories($mode);
$category = getCategoryName($browseNode);
if(!$category||$category==1 Best Selling Books')
{
echo '<hl>Current Best Sellers</hl>';
}
else
{
echo "<hl>Current Best Sellers in $category</hl>";
}
showBrowseNode($browseNode, $page, $mode) ;
Функция showcategories () выводит список избранных категорий, который виден
в верхней части большинства страниц. Функция getCategoryName () возвращает имя
выбранной категории, заданной ее номером browsenode. Функция showBrowseNode ()
отображает страницу книг этой категории.
Давайте рассмотрим функцию showcategories (). Ее код показан в листинге 33.5.
Листинг 33.5. Функция showcategories () из библиотеки categoryfunctions.php —
выводит список категорий
// Выводит начальный список популярных категорий
function showcategories($mode)
{
global $categoryList;
echo '<hrxh2>Selected Categories</h2>';
if($mode == 'books')
{
asort($categoryList);
$categories = count($categoryList);
$columns = 4;
$rows = ceil($categories/$columns);
echo '<table border = 0 cellpadding = 0 cellspacing=O width = "100%"><tr>';
814
Часть V. Реальные проекты на РНР и MySQL
reset($categoryList);
for($col = 0; $col<$columns; $col++)
{
echo '<td width = ”'.{100/Scolumns).’valign = topxub';
for($row = 0; $row<$rows; $row++)
(
$category = each($categoryList);
if($category)
{
$browseNode = $category['key'];
$name = $category['value'];
echo "clixspan class = ' category' ><a href =
'index.php?action=browsenode&browseNode=$browseNode'>$name
</a></spanx/li>” ;
}
)
echo ’ </ulx/td>' ;
)
echo ' </trx/table><hr>1 ;
}
)
Эта функция использует массив categoryList, объявленный в файле
constants .php, для преобразования номеров browsenode в имена категорий. Нужные
номера browsenode просто жестко закодированы в этом массиве. Функция сортирует
массив и отображает различные категории.
Следующая вызываемая в главном цикле обработки событий функция getCate-
goryName () ищет интересующий пользователя номер browsenode, чтобы на экране
можно было отобразить заголовок наподобие Current Best Sellers in Business & Inves-
ting (Текущие бестселлеры категории "Бизнес и инвестиции’’). Поиск выполняется
все в том же массиве categoryList.
Самое интересное начинается, когда дело доходит до функции showBrows eNode (),
код которой можно видеть в листинге 33.6.
Листинг 33.6. Функция ehowBrowseNode () из библиотеки bookdieplayfunctions. php —
список категорий
// Отображение страницы товаров для конкретного узла просмотра
function showBrowseNode($browseNode, $page, $mode)
{
$ars = getARS('browse1, array('browsenode’=>$browseNode,
'page' => $page,
'mode'=>$mode));
showSummary($ars->products(), $page, $ars->totalResults(),
$mode, SbrowseNode);
)
Функция showBrowseNode () выполняет два действия. Во-первых, она вызывает
функцию getARS () из файла cachefunctions .php. Эта функция извлекает и возвра-
щает объект AmazonResultSet (подробнее он будет рассмотрен чуть позже). Затем
Глава 33. Подключение к Web-службам с помощью XML и SOAP
815
она вызывает функции, showSummary () из файла bookdisplayfunctions .php, чтобы
отобразить полученную информацию.
Функция getARS() играет главную роль в работе всего приложения. Если про-
смотреть код остальных действий — просмотра сведений, изображений и выполне-
ния поиска — легко убедиться, что все они обращаются к этой функции.
Извлечение класса AmazonResultSet
Рассмотрим функцию getARS () более подробно. Ее код показан в листинге 33.7.
Листинг 33.7. Функция getARS () из библиотеки cachefunctions.php —
результирующий набор запроса
// Извлечение объекта AmazonResultSet из кэша или непосредственно из запроса.
// Если объект получен непосредственно из запроса, его нужно добавить в кэш
function getARS($type, $parameters)
{
$cache = cached($type, $parameters);
if($cache) // если найдено в кэше
{
return $cache;
)
else
{
$ars = new AmazonResultSet;
if($type == 'asin')
$ars->ASINSearch(padASIN($parameters['asin']), $parameters['mode']);
if($type == 'browse')
$ars->browseNodeSearch($parameters['browsenode'], $parameters['page'],
Sparameters['mode' ] ) ;
if($type == 'search')
$ars->keywordSearch($parameters['search'], $parameters['page'],
$parameters['mode' ]);
cache($type, $parameters, $ars);
)
return $ars;
j
Эта функция предназначена для управления процессом получения данных с сайта
Amazon. Она может это делать двумя способами: либо из кэша, либо непосредственно
с сайта Amazon. Поскольку Amazon требует, чтобы разработчики кэшировали загру-
женные данные, вначале функция ищет данные в кэше. Вскоре мы его рассмотрим.
Если конкретный запрос еще не выполнялся, данные должны быть загружены не-
посредственно с сайта Amazon. Это выполняется путем создания экземпляра класса
AmazonResultSet и вызова применительно к нему метода, соответствующего кон-
кретному запросу, который нужно выполнить. Тип запроса определяется параметром
$type. В приведенном примере выполнения поиска категории (узла просмотра) в ка-
честве значения этого параметра было передано значение browse (см. листинг 33.6).
Чтобы выполнить запрос по конкретной книге, в качестве значения этого параметра
необходимо передать asin, а чтобы выполнить поиск по ключевому слову — значение
search.
816
Часть V. Реальные проекты на РНР и MySQL
Каждый из этих параметров приводит к вызову своего метода класса AmazonRe-
sultSet. Поиск отдельного элемента вызывает метод ASINSearch (). Поиск категории
обращается к методу browseNodeSearch(), а поиск по ключевому слову — к методу
keywordsearch().
Рассмотрим класс AmazonResultSet более подробно. Полный код этого класса по-
казан в листинге 33.8.
Листинг 33.8. AmazonResultSet. php — класс, предназначенный для обработки
соединений с сайтом Amazon
<?php
// Используя набор констант из файла constants.php, можно
// переключаться между методами REST и SOAP
if(METHOD=='SOAP')
{
include_once('nusoap/nusoap.php');
}
// Этот класс хранит результаты запросов
// Как правило, это от 1 до 10 экземпляров класса Product
class AmazonResultSet
{
private $browseNode;
private $page;
private $mode;
private $url;
private $type;
private $totalResults;
private $currentProduct = null;
private $products = array О; // массив объектов Product
function { return ) products() $ thi s->produc t s;
function { totalResults()
return } $this->totalResults;
function getProduct($i)
{
if(isset($this->products[$i]))
return $this->products[$i] ;
else
return false;
}
// Выдача запроса для получения страницы со списком товаров узла просмотра.
// Переключение между методами XML/HTTP и SOAP в файле constants.php
// Возврат массива объектов Products
function browseNodeSearch($browseNode, $page, $mode)
Глава 33. Подключение к Web-службам с помощью XML и SOAP
817
if(METHOD=='SOAP')
(
Ssoapclient = new soapclient (
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl',
1wsdl1);
$soap_proxy = $soapclient->getProxy();
$parameters['mode']=$mode;
$parameters[’page']=$page;
$parameters['type'] = 1 heavy';
$parameters['tag']=$this->assocID;
Sparameters['devtag']=$this->devTag;
$parameters['sort']='+salesrank';
$parameters['browse_node'] = $browseNode;
// Действительное выполнение запроса soap
$result = $soap_proxy->BrowseNodeSearchRequest ($parar.eters
if(isSOAPError(Sresult))
return false;
$this->totalResults = Sresult['TotalResults'];
foreach(Sresult['Details'] as Sproduct)
{
$this->products[] = new Product(Sproduct);
)
unset(Ssoapclient);
unset($soap_proxy):
else
// Формирование URL-адреса и вызов функции parseXML
// для его загрузки и анализа
$this->type = 'browse';
$this->browseNode = SbrowseNode;
$this->page = Spage;
$this->mode = $mode;
$this->url = 'http://xml.amazon.com/onca/xml2?t='.ASSOCIATEID
.’&dev-t='.DEVTAG.'&BrowseNodeSearch='
,$this->browseNode.'&mode='.$this->mode
.'&type=heavyfcpage='.$this->page
.'&sort=+salesrank&f=xml';
$this->parseXML();
return $this->products;
//Извлечение URL-адреса большого изображения для указанного идентификатора ASIN
// Возврат строки
function getlmageUrlLarge($ASIN, Smode)
{
foreach($this->products as Sproduct)
{
if ( Sproduct->ASIN()== $ASIN)
return $product->imageURLLarge();
818
Часть V. Реальные проекты на PHP и MySQL
// Если элемент не найден
$ thi s - >ASINSearch ($AS IN, Smode) ;
return $this->products(0)->imageURLLarge();
}
/ Выполнение запроса для извлечения товаров с указанным ASIN
/ •' Переключение между режимами XML/HTTP и SOAP в файле constants .php
'/ Возврат объекта Products
function ASINSearch($ASIN, $mode = ’books')
{
$this->type = 'ASIN';
$this->ASIN=$ASIN;
$this->mode = $mode;
SASIN = padASIN($ASIN);
if(METHOD=='SOAP')
{
error_reporting(E_ALL & ~E_NOTICE);
$soapclient = new soapclient (
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl’,
'wsdl') ;
$soap_proxy = $soapclient->getProxy();
Sparameters['asin']=$ASIN;
$parameters['mode']=$mode;
$parameters['type']="heavy";
Sparameters['tag']=$this->assocID;
$parameters['devtag']=$this->devTag;
// Действительное выполнение запроса soap
Sresult = $soap_proxy->AsinSearchRequest(Sparameters);
if(isSOAPError(Sresult))
(
print_r(Sresult);
return false;
}
$this->products[0] = new Product(Sresult['Details'][0]);
$this->totalResults=l ;
unset(Ssoapclient);
unset($soap_proxy);
)
else
{
//Формирование URL-адреса и вызов функции parseXML для его загрузки и анализа
$this->url = 'http://xml.amazon.com/onca/xml2?t='.ASSOCIATEID
.'&dev-t='.DEVTAG.’&AsinSearch='
.$this->ASIN
.'&type=heavy&f=xml';
$this->parseXML();
}
return $this->products[0];
)
I/ Выполнение запроса для получения страницы с результатами поиска
// по ключевому слову
Глава 33. Подключение к Web-службам с помощью XML и SOAP
819
// Переключение между режимами XML/HTTP и SOAP в файле index.php
// Возврат массива объектов Products
function keywordsearch(Ssearch, $page, $mode = ’books')
{
if(METHOD=='SOAP')
{
error_reporting(E_ALL & ~E_NOTICE);
$soapclient = new soapclient(
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl' , 'wsdl' ) ;
Ssoap_proxy = $soapclient->getProxy();
$parameters['mode']=$mode;
$parameters['page']=$page;
$parameters['type']="heavy";
$parameters['tag']=$this->assocID;
$parameters['devtag']=$this->devTag;
$parameters['sort']='+salesrank';
$parameters['keyword'] = Ssearch;
// Действительное выполнение запроса soap
Sresult = $soap_proxy->KeywordSearchRequest(Sparameters);
if(isSOAPError(Sresult) )
return false;
foreach(Sresult['Details'] as Sproduct)
{
$this->products[] = new Product(Sproduct):
}
$this->totalResults = Sresult['TotalResults'] ;
unset(Ssoapclient);
unset ($soap__proxy) ;
}
else
{
$this->type = 'search';
Sthis->search=Ssearch;
$this->page = Spage;
Ssearch = urlencode(Ssearch);
Sthis->mode = $mode;
$this->url = 'http://xml.amazon.com/onca/xml2?t='.ASSOCIATEID
.'&dev-t='.DEVTAG.'&KeywordSearch='
.Ssearch.'&mode='.$this->mode
.'&type=heavy&page='
.$this->page
.'&sort=+salesrank&f=xml';
$this->parseXML();
}
return $this->products;
}
//Синтаксический разбор XML-кода с помещением результата в объект(ы) Product
function parseXMLO
{
// Подавление ошибок, поскольку здесь иногда случаются сбои
$xml = @simplexml_load_file($this->url);
820
Часть V. Реальные проекты на РНР и MySQL
if(!$xml)
{
// Повторная попытка на случай, если сервер занят
$xml = @simplexml_load_file($this->url);
if(!Sxml)
{
return false;
)
}
$this->totalResults = (integer)$xml->TotalResults;
foreach($xml->Details as $productXML)
{
$this->products[] = new Product(SproductXML);
}
}
}
Этот полезный класс выполняет именно те действия, для выполнения которых и
предназначены классы. Он служит своего рода "черным ящиком” для интерфейса к
сайту Amazon. Внутри класса подключение к сайту Amazon может выполняться с ис-
пользованием REST или SOAP. Применяемый метод определяется глобальной кон-
стантой METHOD, устанавливаемой в файле constants . php.
Рассмотрение кода класса производится на примере поиска категории. Класс
AmazonResultSet используется следующим образом:
$ars = new AmazonResultSet;
Sars->browseNodeSearch(Sparameters['browsenode'],
Sparameters['page'] ,
Sparameters['mode']);
Этот класс не имеет конструктора, поэтому можно сразу переходить к методу
browseNodeSearch (). Мы передаем ему три параметра: интересующий нас номер
browsenode (который соответствует, например, категории "Business & Investing”
("Бизнес и инвестиции”) или "Computers & Internet” ("Компьютеры и Internet”)), но-
мер страницы, представляющей записи, которые желательно извлечь, и режим, ко-
торый представляет интересующий нас тип товара. Фрагмент кода этого метода по-
казан в листинге 33.9.
Листинг 33.9. Метод browseNodeSearch () — выполнение поиска категории
function browseNodeSearch(SbrowseNode, Spage, Smode)
(
if(METHOD=='SOAP')
{
Ssoapclient = new soapclient(
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl',
'wsdl');
$soap_proxy = $soapclient->getProxy();
Sparameters!'mode']=Smode;
Sparameters[ 'page' ] =$page,-
Sparameters['type']='heavy';
Sparameters[’tag']=$this->assocID;
Глава 33. Подключение к Web-службам с помощью XML и SOAP
821
Sparameters['devtag']=$this->devTag;
Sparameters['sort']='+salesrank';
Sparameters['browse_node'] = SbrowseNode;
// Действительное выполнение запроса soap
Sresult = $soap__proxy->BrowseNodeSearchRequest (Sparameters) ;
if(isSOAPError(Sresult))
return false;
$this->totalResults = Sresult['TotalResults'];
foreach(Sresult[1 Details'] as Sproduct)
{
$this->products[] = new Product(Sproduct);
}
unset(Ssoapclient);
unset($soap_proxy);
}
else
{
//Формирование URL-адреса и вызов функции parseXML для его загрузки и анализа
$this->type = 'browse';
$this->browseNode = SbrowseNode;
$this->page = $page;
$this->mode = $mode;
$this->url = 'http://xml.amazon.com/onca/xml2?t='.ASSOCIATEID
.'&dev-t='.DEVTAG.'&BrowseNodeSearch='
.$this->browseNode.'&mode='.$this->mode
.'&type=heavy&page='.$this->page
.'&sort=+salesrank&f=xml';
$this->parseXML();
}
return $this->products;
}
В зависимости от значения константы METHOD этот метод выполняет запрос с ис-
пользованием REST или SOAP. Рассмотрим последовательно оба варианта.
Использование метода REST /XML поверх HTTP
Чтобы можно было использовать REST/XML поверх HTTP, вначале потребуется
определить несколько важных переменных членов класса:
type — тип поиска, который нужно выполнить. Мы выполняем поиск книг внут-
ри конкретной категории browsenode, поэтом}’ устанавливаем это значение рав-
ным browse.
browse — значение конкретного узла просмотра browsenode, который был пере-
дан в качестве параметра.
раде — номер страницы, переданный в качестве параметра.
mode — тип искомого элемента (например, books), переданный в качестве пара-
метра.
url — URL-адрес на сайте Amazon, к которому нужно подключиться для выпол-
нения данного типа поиска.
822
Часть V. Реальные проекты на РНР и MySQL
Перечень URL-адресов, к которым следует устанавливать HTTP-соединения для
выполнения различных типов поиска, можно найти в документе "Amazon.com Web
Services API and Integration Guide” ("Руководство no API-интерфейсу и интеграции с
Web-службами Amazon.com”) в комплекте средств разработчика. Рассмотрим пара-
метры, переданные методом GET:
$this->_url = 'http://xml.amazon. com/onca/xml2?t= ' .ASSOCIATEID
.'&dev-t='.DEVTAG.'&BrowseNodeSearch='
.$this->_browseNode.'&mode=1.$this->_mode
.'&type=heavy&page='.$this->_page
.'&sort=+salesrank&f=xml';
Этому URL-адресу необходимо передать следующие параметры:
t — ваш идентификатор Associate ID.
dev -1 — ваша метка разработчика.
BrowseNodeSearch — номер browsenode, поиск которого должен быть выполнен.
mode—books или другой допустимый тип товара.
type — Heavy или lite (обратите внимание на правильность ввода!). Heavy пре-
доставляет больший объем информации.
раде — группа из десяти результатов.
sort — порядок, в котором должны быть возвращены результаты. Этот параметр
не обязателен. В данном случае мы установили его равным +salesrank, чтобы
результаты были возвращены в порядке категорий продаж.
f — формат. Этот параметр должен всегда содержать значение 'xml'.
Допустимы следующие типы сортировки:
По типовым элементам: t-pmrank
По категориям успешности продажи: + sales rank
По отзывам рядовых клиентов: rreviewrank
По цене (от низкой к высокой): +pricerank
По цене (от высокой к низкой): +inverse-pricerank
По дате публикации: rdaterank
По алфавиту (A—Z ): rtitlerank
По алфавиту (Z—A):-titlerank
После того, как все эти параметры определены, мы вызываем метод
$this->parseXML();
чтобы в действительности выполнить разбор XML-документа. Код метода parseXML ()
показан в листинге 33.10.
Листинг 33.10. Метод parseXML ()— разбор XML-документа, возвращенного в
результате запроса
// Анализ XML-документа с помещением результата в объект(ы) Product
function parseXML()
{
Глава 33. Подключение к Web-службам с помощью XML и SOAP
823
// Подавление ошибок, поскольку здесь иногда случаются сбои
$xml = @simplexml_load_file($this->url);
if(!Sxml)
{
// Повторная попытка на случай, если сервер занят
$xml = @simplexml_load_file($this->url);
iff!$xml)
{
return false;
}
}
$this->totalResults = (integer)$xml->TotalResults,•
foreach($xml->Details as SproductXML)
{
$this->products[] = new Product(SproductXML);
)
Функция simplexml_load_file() выполняет большую часть необходимых дейст-
вий. Она считывает XML-содержимое из файла или, как в данном случае — из URL-
адреса. Функция предоставляет объектно-ориентированный интерфейс доступа к
данным и структурам, хранящимся в XML-документе. Этот интерфейс доступа к дан-
ным весьма полезен, но поскольку нам требуется, чтобы с данными, поступающими с
помощью REST или SOAP, работал только один набор функций интерфейса, можно
построить собственный объектно-ориентированный интерфейс доступа к тем же
данным в экземплярах класса Product. Обратите внимание, что в REST-версии атри-
буты, полученные из XML-документа, преобразуются в переменные РНР соответст-
вующих типов. В РНР операция cast не применяется, но если ее не использовать в
данном случае, метод извлек бы объектные представления всех элементов данных, в
том числе и тех, которые не слишком нужны.
Класс Product содержит в основном функции доступа к данным, хранящимся в его
приватных членах, поэтому приведение всего файла не имеет особого смысла. Одна-
ко целесообразно рассмотреть структуру класса и его конструктор. Часть определе-
ния класса Product показана в листинге 33.11.
Листинг 33.11. Класс Product содержит полученную информацию о товаре,
предлагаемом сайтом Amazon
class Product
{
private $ASIN;
private SproductName;
private SreleaseDate;
private Smanufacturer;
private SimageUrlMedium;
private SimageUrlLarge;
private SlistPrice;
private SourPrice;
private SsalesRank;
private Savailability;
private SavgCustomerRating;
private Sauthors = arrayf);
824
Часть V. Реальные проекты на РНР и MySQL
private $reviews = array();
private $similarProducts = arrayO;
function __construct($xml)
{
if(METHOD=='SOAP')
{
$this->ASIN = $xml['Asin'];
$this->productName = $xml['ProductName'];
if($xml['Authors'])
{
foreach($xml['Authors1] as $author)
{
$this->authors[] = $author;
J
}
$this->releaseDate = $xml['ReleaseDate'l;
$ this-manufacturer = $xml['Manufacturer;
$this->imageUrlMedium = $xml['ImageUrlMedium’];
$this->imageUrlLarge = $xml['ImageUrlLarge'];
$this->listPrice = $xml['ListPrice;
$this->listPrice = str_replace('$', '', $this->listPrice);
$this->listPrice = str_replace(',', $this->listPrice);
$this->listPrice = floatval($this->listPrice);
$this->ourPrice = $xml['OurPrice'];
$this->ourPrice = str_replace(’$', '', $this->ourPrice);
$this->ourPrice = str_replace(',', $this->ourPrice);
$this->ourPrice = floatval($this->ourPrice);
$this->salesRank = $xml[1SalesRank'];
$this->availability = $xml['Availability'j;
$this->avgCustomerRating = $xml['Reviews’1['AvgCustomerRating'];
$reviewCount = 0;
if($xml[’Reviews 1][1CustomerReviews'])
{
foreach ($xml['Reviews']['CustomerReviews'] as $review)
{
$this->reviews[$reviewCount]['rating'] = $review['Rating'];
$this->reviews[$reviewCount]['summary') = $review['Summary’];
$this->reviews[$reviewCount]['comment'] = $review['Comment'];
$reviewCount++;
}
}
if($xml['SimilarProducts'])
{
foreach ($xml['SimilarProducts'] as $similar)
{
$this->similarProducts[] = $similar;
}
}
}
else // использование метода REST
{
Глава 33. Подключение к Web-службам с помощью XML и SOAP
825
$this->ASIN = (string)$xml->Asin;
$this->productName = (string)$xml->ProductName;
if($xml->Authors->Author)
{
foreach($xml->Authors->Author as $author)
{
$this->authors[] = (string)$author;
}
}
$this->releaseDate = (string)$xml->ReleaseDate;
$this->manufacturer = (string)$xml->Manufacturer;
$this->imageUrlMedium = (string)$xml->ImageUrlMedium;
$this->imageUrlLarge = (string)Sxml->ImageUrlLarge:
$this->listPrice = (string)$xml->ListPrice;
$this->listPrice = str_replace('$', '', $this->listPrice);
$this->listPrice = str_replace(',', '', $this->listPrice);
$this->listPrice = floatval($this->listPrice);
$this->ourPrice = (string)$xml->OurPrice;
$this->ourPrice = str_replace('$', $this->ourPrice);
$this->ourPrice = str_replace(',', '', $this->ourPrice);
$this->ourPrice = floatval($this->ourPrice);
$this->salesRank = (string)$xml->SalesRank;
$this->availability = (string)$xml->Availability;
$this->avgCustomerRating = (float)$xml->Reviews->AvgCustomerRating;
$reviewCount = 0;
if($xml->Reviews~>CustomerReview)
{
foreach ($xml->Reviews->CustomerReview as $review)
{
$this->reviews[$reviewCount]['rating'] = (float)$review->Rating;
$this->reviews[$reviewCount]['summary'] = (string)$review->Summary;
$this->reviews[SreviewCount]['comment'] = (string)$review->Comment;
$reviewCount++;
}
}
if($xml->SimilarProducts->Product)
{
foreach ($xml->SimilarProducts->Product as $similar)
{
$this->similarProducts[] = (string)$similar;
}
}
Рассматриваемый конструктор также принимает две различные формы входных
данных и создает один интерфейс приложения. Обратите внимание, что хотя часть
обрабатывающего кода можно было бы сделать более общей, некоторые "экзотиче-
ские” атрибуты, такие как отзывы (reviews), имеют различные имена в зависимости
от используемого метода.
826
Часть V. Реальные проекты на РНР и MySQL
По завершении всей обработки, связанной с извлечением данных, управление
снова передается функции getARS (), и, следовательно, методу showBrows eNode ().
Следующий шаг состоит в вызове функции:
showSummary($ars->products(), Spage,
$ars->totalResults(), $mode,
$browseNode);
Функция showSummary () просто отображает данные класса AmazonResultSet, как
это было описано, начиная с рис. 33.1. Поэтому код ее здесь не рассматривается.
Использование метода SOAP
Вернемся назад и рассмотрим SOAP-версию функции browseNodeSearch (). Ниже
этот фрагмент кода показан еще раз:
Ssoapclient = new soapclient(
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl','wsdl');
$soap_proxy = $soapclient->getProxy();
Sparameters['mode']=$mode;
Sparameters['page']=Spage;
Sparameters['type']='heavy';
Sparameters!'tag']=$this->assocID;
Sparameters['devtag']=$this->devTag;
Sparameters['sort']='+salesrank';
Sparameters['browse_node'] = SbrowseNode;
// Действительное выполнение запроса soap
Sresult = $soap_proxy->BrowseNodeSearchRequest (Sparameters)
if(isSOAPError(Sresult))
return false;
$this->totalResults = Sresult['TotalResults'];
foreach($result['Details'] as Sproduct)
{
$this->products[] = new Product(Sproduct);
}
unset(Ssoapclient);
unset($soap_proxy);
Эта версия не использует никаких дополнительных функций; клиент SOAP вы-
полняет все необходимые действия.
Работа функции начинается с создания клиента SOAP:
Ssoapclient = new soapclient(
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl','wsdl');
В данном случае мы передаем клиенту два параметра. Первый — WSDL-описание
службы, а второй параметр сообщает клиенту SOAP, что это — URL-адрес WSDL.
Можно было бы также передать только один параметр: конечный пункт службы, яв-
ляющийся непосредственным URL-адресом сервера SOAP.
Данный подход был выбран по веской причине, которая становится понятной из
следующей строки кода:
$soap_proxy = $soapclient->getProxy();
Глава 33. Подключение к Web-службам с помощью XML и SOAP
827
Эта строка создает класс в соответствии с информацией, хранящейся в WSDL-
документе. Этот класс, SOAP-прокси, будет содержать методы, которые соответству-
ют методам Web-службы. В результате задача программиста существенно упрощается.
Теперь с Web-службой можно взаимодействовать так, как если бы она была локаль-
ным классом РНР.
Затем мы определяем массив параметров, которые нужно передать запросу
browsenode:
Sparameters!'mode']=$mode;
Sparameters['page']=Spage;
Sparameters['type']='heavy';
Sparameters['tag']=$this->assocID;
Sparameters!'devtag']=$this->devTag;
Sparameters['sort']='+salesrank';
Sparameters['browse_node'] = SbrowseNode;
Использование класса proxy позволяет просто вызывать методы Web-службы, пе-
редавая им массив параметров:
Sresult = Ssoap_proxy->BrowseNodeSearchRequest(Sparameters);
Данные, сохраненные в переменной Sresult, представляют собой массив, кото-
рый можно хранить в массиве products класса AmazonResultSet непосредственно как
объект Product.
Кэширование данных
Теперь вернемся к функции getARS () и реализуем кэширование. Как вы, вероят-
но, помните, эта функция выглядит следующим образом:
// Извлечение объекта AmazonResultSet из кэша или непосредственно из запроса.
// Если объект получен непосредственно из запроса, его нужно добавить в кэш
function getARS($type, Sparameters)
{
Scache = cached($type, Sparameters);
if(Scache) // если найдено в кэше
{
return Scache;
J
else
{
$ars = new AmazonResultSet;
if($type == 'asin')
$ars->ASINSearch(padASIN(Sparameters!'asin']), Sparameters['mode']);
if($type == 'browse')
$ars->browseNodeSearch(Sparameters['browsenode'],
Sparameters['page'], Spa ramet ers['mode']);
if($type == 'search')
$ars->keywordSearch(Sparameters!'search'], Sparameters!'page'],
Sparameters['mode']);
cache($type, Sparameters, Sars);
)
return $ars;
)
828
Часть V. Реальные проекты на РНР и MySQL
В приложении все кэширование данных, полученных с использованием SOAP или
XML, выполняется этой функцией. Еще одна функция служит для кэширования изо-
бражений. Код начинается с вызова функции cached () для проверки того, помещен
ли уже необходимый объект AmazonResultSet в кэш. Если да, то вместо обращения к
сайту Amazon эти данные просто возвращаются:
Scache = cached($type, Sparameters);
if (Scache) // если найдено в кэше
{
return Scache;
)
В противном случае после получения данных с сайта Amazon они добавляются в
кэш:
cache($type, Sparameters, $ars);
Рассмотрим две функции, cached () и cache (), более подробно. Эти функции, код
которых показан в листинге 33.12, реализуют кэширование, оговоренное в условиях,
предъявляемых компанией Amazon.
Листинг 33.12. Функции cached () и cache () из библиотеки cachefunctions. php —
реализуют кэширование
// Проверка наличия данных Amazon в кэше.
// Если данные есть в кэше, они возвращаются,
// если нет - возвращается значение false
function cached($type, Sparameters)
{
if($type == 'browse')
{
Sfilename = CACHE.'/browse.'.Sparameters['browsenode'].’.1
.Sparameters['pageSparameters['mode'].'.dat'
)
if($type == 'search')
(
Sfilename = CACHE.’/search.'.Sparameters['search'].'.'-Sparameters['page'].
'.'.Sparameters['mode'].'.dat’ ;
}
if($type == 'asin')
{
Sfilename = CACHE.'/asin.'.Sparameters['asin'].'.'.Sparameters['mode'].
'.dat';
)
// He отсутствуют ли кэшированные данные или не хранятся ли они дольше 1 часа?
if(! file_exists(Sfilename) ||
((mktimeO - filemtimefSfilename)) > 60*60))
{
return false;
)
Sdata = file_get_contents(Sfilename);
return unserialize(Sdata);
Глава 33. Подключение к Web-службам с помощью XML и SOAP
829
11 Добавление данных Amazon в кэш
function cache($type, $parameters, $data)
{
if($type == 'browse')
{
$filename = CACHE.'/browse.'.Sparameters['browsenode'].
'.'.$parameters['pageSparameters['mode'].'.dat';
}
if($type == 'search')
{
Sfilename = CACHE.'/search.'.Sparameters['search'].'.'.Sparameters['page'].
'.'.Sparameters['mode'].'.dat';
}
if($type == 'asin')
{
Sfilename = CACHE.'/asin.'.Sparameters['asin'].'.'.Sparameters)'mode'].
' . dat' ;
}
$data = serialize(Sdara);
$fp = fopen(Sfilename, 'wb');
if(!$fp||(fwrite($fp, $data)==-l))
{
echo ('<p>Error, could not store cache file');
}
fclose(Sfp);
Из этого кода видно, что файлы кэша хранятся под именами, образованными из
типа запроса и параметров запроса. Функция cache () сохраняет результаты, преоб-
разуя их в последовательную форму, а функция cached)) выполняет обратное преоб-
разование. Кроме того, в соответствии с предъявляемыми условиями, функция
cached () будет также перезаписывать любые данные, хранящиеся в кэше дольше 1
часа.
Функция serialize() преобразует хранимые данные программы в строку, кото-
рая может быть сохранена. В данном случае мы создаем доступное для сохранения на
диске представление объекта AmazonResultSet. Функция unserialize () выполняет
обратное действие, преобразуя сохраненную версию обратно в структуру данных в
памяти. Обратите внимание, что подобное преобразование объекта из последова-
тельной формы означает необходимость наличия в файле определения класса, чтобы
сразу после перезагрузки класс был доступен для обработки и использования.
В этом приложении извлечение результирующего набора из кэша занимает доли
секунды, в то время как выполнение нового реального запроса требует до 10 секунд.
Реализация покупательской тележки
Итак, что же можно делать с помощью всех этих замечательных возможностей по
выдаче запросов к Amazon? Наиболее очевидной возможностью будет построение
покупательской тележки. Поскольку эта тема подробно рассматривалась в главе 27,
глубоко вникать в принципы функционирования покупательской тележки мы уже не
будем.
830
Часть V. Реальные проекты на РНР и MySQL
Код функций покупательской тележки показан в листинге 33.13.
Листинг ЗЗЛЗ. cartfunctions.php — реализация покупательской тележки
<?php
require_once('AmazonResultSet.php');
// Функция showSummary!), определенная в файле bookdisplay.php, отображает
// текущее содержимое покупательской тележки
function showCart(Scart, Smode)
I
// Построение массива, который нужно передать
Sproducts = array!),-
foreach!Scart as $ASIN=>$product)
{
$ars = getARS!'asin', array('asin'=>$ASIN, 'mode'=>$mode));
if($ars)
$products[] = $ars->getProduct (0) ,-
}
// Построение формы для подключения к покупательской тележке сайта Amazon.com
echo '<form method="POST"
action="http:I/www.amazon.com/o/dt/assoc/handle-buy-box">';
foreach($cart as $ASIN=>$product)
{
Squantity = $cart[$ASIN]['quantity'];
echo "<input type='hidden' name='asin.$ASIN' value='Squantity'>";
}
echo ’<input type=“hidden" name="tag-value" value="ASSOClATElD“>';
echo 'cinput type="hidden" name="tag_value" value=”ASSOCIATElD">';
echo '<input type=”image" src="images/checkout.gif"
name=”submit.add-to-cart”
value=”Buy From Amazon.com">';
echo ' When you have finished shopping press checkout to add all the
items in your Tahuayo cart to your Amazon cart and complete
your purchase.<br />';
echo '</form>';
echo '<a href - "index.php?action=emptycart"ximg
src = "images/emptycart.gif" alt = "Empty Cart" border = 0></a>
If you have finished with this cart, you can empty it of all items.
<br / >' ;
echo '<hl>Cart Contents</hl>';
showSummary(Sproducts, 1, count(Sproducts), Smode, 0, true);
// Вывод краткого содержимого тележки, которое всегда присутствует на экране.
//В нем отображаются только последних три элемента из числа добавленных
function showSmallCart()
{
global $_SESSION;
echo '<table border = 1 cellpadding = 1 cellspacing = 0>' ;
echo ' <trxtd class = cartheading>Your Cart $ ' .
number_format(cartPrice(), 2).
' </tdx/tr> ' ;
Глава 33. Подключение к Web-службам с помощью XML и SOAP
831
echo '<trxtd class = cart>'.cartcontents().'</td></tr>';
// Форма для связи с покупательской тележкой Amazon.com
echo '<form method=,,POST”
action=“http://www.amazon.com/o/dt/assoc/handle—buy-box">';
echo '<trxtd class = cartheadingxa href =
“index.php?action=showcart"ximg
src=”images/details.gif" border=0x/a>' ;
foreach($_SESSION['cart'] as $ASlN=>$product)
{
Squantity = $_SESSION['cart'][$ASIN]['quantity'];
echo "<input type='hidden' name='asin.$ASIN' value='Squantity'>";
}
echo '<input type="hidden" name="tag-value" value="ASSOCIATEID">';
echo '<input type="hidden" name="tag_value" value="ASSOCIATEID">';
echo '<input type="image" src="images/checkout.gif"
name="submit.add-to-cart" value=”Buy From Amazon.com''>';
echo ' </tdx/tr>' ;
echo '</form>';
echo '</table>';
}
// Вывод трех последних элементов, добавленных в тележку
function cartContents()
{
global $_SESSION;
$display = array_slice($_SESS!ON['cart'], -3, 3);
// Эти элементы должны отображаться в обратном хронологическом порядке
$display = array_reverse($display, true);
$result = '';
$counter = 0;
// Сокращение названий, если они слишком длинные
foreach($display as $product)
{
if (strlen(Sproduct['name'])< = 40)
Sresult .= Sproduct)'name'].'<br />';
else
Sresult .= substr(Sproduct)'name'], 0, 37).'...<br />';
$counter++;
}
// Добавление пустых строк, если тележка почти пуста, чтобы
// размер области отображения оставался неизменным
for(;$counter<3; $counter++)
{
Sresult .= '<br />';
}
return Sresult;
}
// Вычисление общей стоимости товаров, помещенных в тележку
function cartPrice()
{
832
Часть V. Реальные проекты на РНР и MySQL
global $_SESSION;
$total = 0.0;
foreach($_SESSION['cart'] as Sproduct)
{
Sprice = str_replace('$', '', Sproduct['price']);
Stotal += $price*$product['quantity'];
}
return $total;
}
// Добавление в тележку одного элемента.
//В настоящее время возможность одновременного добавления
// более одного элемента отсутствует
function addToCart(&$cart, $ASIN, $mode)
{
if(isset($cart[$ASIN] ))
(
$cart[$ASIN]['quantity'] +=1;
}
else
{
// Проверка допустимости идентификатора ASIN
//и выяснение стоимости данного товара
$ars = new AmazonResultSet;
Sproduct = $ars->ASINSearch($ASIN, $mode);
if($product->valid())
$cart[$ASIN] = array('price'=>$product->ourPrice(),
'name' => $product->productName(), 'quantity' => 1) ;
}
}
// Удаление из тележки отдельного элемента
function deleteFromCart(&$cart, $ASIN)
{
unset (Scart[$ASIN]);
}
?>
Выполнение действий с помощью этой тележки характеризуется рядом отличий.
Например, рассмотрим функцию addloCart (). При попытке добавления элемента в
тележку можно проверить допустимость его идентификатора ASIN и выяснить его
текущую (или, по меньшей мере, кэшированную) стоимость.
Особый интерес вызывает следующий вопрос: как пользователи передают свои
данные сайту Amazon при оплате товара?
Оплата на сайте Amazon
Давайте внимательно рассмотрим функцию showCart (), код которой показан в
листинге 33.13. Нас интересует следующий фрагмент кода:
Глава 33. Подключение к WBb-службам с помощью XML и SOAP
833
//Построение формы для подключения к покупательской тележке сайта Amazon.com
echo '<form method="POST"
action=”http://www.amazon.com/o/dt/assoc/handle-buy-box">';
foreach($cart as $ASIN=>$product)
{
$quantity = $cart[$ASIN]['quantity'];
echo ”<input type='hidden' name='asin.$ASIN' value='$quantity'>";
}
echo '<input type="hidden" name=”tag-value” value= "ASSOCIATEID”>' ;
echo '<input type="hidden" name="tag_value" value="ASSOCIATEID">';
echo '<input type="image" src="images/checkout.gif"
name="submi t.add-to-cart"
value=”Buy From Amazon.com">';
echo When you have finished shopping press checkout to add all the
items in your Tahuayo cart to your Amazon cart and complete
your purchase.<br />';
echo '</form>';
Кнопка оплаты Checkout — это кнопка формы, которая осуществляет подключе-
ние нашей тележки к покупательской тележке клиента на сайте Amazon. Через POST-
переменные мы передаем идентификаторы ASIN, количество выбранных товаров и
свой идентификатор Associate ID. Дело сделано! Результат щелчка на этой кнопке по-
казан на рис. 33.5 ранее в этой главе.
Одна из проблем использования этого интерфейса состоит в том, что он обеспе-
чивает передачу данных только в одном направлении. Вы можете добавлять элемен-
ты в покупательскую тележку сайта Amazon, но не можете их удалять. То есть пользо-
ватели не могут переходить с одного сайта на другой и обратно, не создавая при этом
дубликаты элементов в своих тележках.
Инсталляция кода проекта
Для инсталляции кода проекта, описанного в этой главе, потребуется выполнить
ряд дополнительных действий. После того, как файлы приложения помещены в со-
ответствующий каталог на сервере, необходимо выполнить следующие вещи:
Создать каталог кэша.
Установить для каталога кэша разрешения, чтобы сценарии могли выполнять в
него запись.
Внести изменения в файл constants. php, указав местоположение кэша.
Подписаться на метку разработчика сайта Amazon.
Внести изменения в файл constants. php, указав в нем свою метку разработчика
и, при желании, идентификатор Associate ID.
Убедиться в том, что библиотека NuSOAP установлена. Мы поместили ее внутрь
каталога Tahuayo, но ее можно перемещать и изменять код.
Убедиться в том, что пакет РНР5 скомпилирован с поддержкой simpleXML.
834
Часть V. Реальные проекты на РНР и MySQL
Расширение проекта
Данный проект можно было бы расширить множеством способов:
Можно было бы расширить типы доступных поисков.
Можно поэкспериментировать с Web-службами XSLT сайта Amazon.
В разделе Web Services How-To сайта Amazon можно было бы поискать ссылки на
образцы новаторских приложений. Чтобы ознакомиться с новыми идеями, ре-
комендуем обратиться к следующим приложениям:
http://associates.amazon.сот/exec/panama/associates/ntg/browse/-/567634/
Покупательские тележки — это наиболее очевидный способ применения данной
информации, однако далеко не единственный.
Дополнительные источники информации
Языку' XML и Web-службам посвящено множество книг ц сетевых ресурсов. Пре-
красной отправной точкой для поиска информации может служить сайт консорциума
W3C. Поиск информации можно начать с просмотра страницы рабочей группы по
разработке XML:
http://www.w3.org/XML/Core/
и страницы, посвященной использованию Web-служб:
http: / ,’www.w3 . org/2002 /ws/
Глава 33. Подключение к Web-службам с помощью XML и SOAP
835
Приложения
Приложение А. Инсталляция РНР и MySQL
Приложение Б. Ресурсы в Web
Инсталляция
РНР и MySQL
Версии Apache, РНР и MySQL существуют для очень многих комбинаций опера-
ционных систем и Web-серверов. В этом приложении вы найдете практическую
методику установки Apache, РНР и MySQL на различных серверных платформах.
Кроме того, мы рассмотрим наиболее распространенные варианты для платформ на
базе операционных систем Unix и Windows ХР.
В этом приложении, помимо прочих, рассматриваются следующие темы:
Запуск РНР как CGI-интерпретатора или модуля.
Инсталляция Apache, SSL, РНР и MySQL на Unix-машине.
Инсталляция Apache, РНР и MySQL на Windows-машине.
Проверка работоспособности РНР: функция phpinf о ().
Добавление РНР к серверу’ Microsoft Internet Information Server.
Инсталляция PEAR.
Настройка других конфигураций.
Цель материала, приведенного в этом приложении, заключается в предоставле-
нии своего рода краткого руководства по инсталляции Web-сервера, полезного для
тех, кто занимается поддержкой множества Web-сайтов. Некоторые сайты, как было
показано на примерах, для реализации функций электронной коммерции требуют
использования слоя защищенных сокетов (Secure Socket Layer — SSL). Большинство
из таких сайтов управляется сценариями, которые подключаются к серверу баз дан-
ных, а затем запрашивают и обрабатывают полученные данные.
Многим пользователям вовсе не требуется устанавливать РНР на своих компьюте-
рах, и именно поэтому данные сведения приводятся в приложении, а не, скажем, в
первой главе. Наиболее простой способ получить доступ к надежному серверу через
высокоскоростное соединение, с установленным на сервере РНР, сводится лишь к
простой регистрации у бесчисленного множества компаний, предлагающих Web-
хостинг практически в любой точке земного шара.
В зависимости от причин, по которым приходится устанавливать РНР на своем
компьютере, существует множество решений. Если вы располагаете машиной, посто-
янно подключенной к сети, и планируете использовать ее в качестве сервера, на пер-
838
Часть VI. Приложения
вый план выходит проблема производительности. Если же вы пытаетесь создать сервер
для разработки Web-приложений, который будет служить эффективным инструментом
как для собственно разработки, так и для тестирования кода, то первейшей задачей
оказывается наличие возможности воспроизвести конфигурации, которые встречают-
ся в реальных ситуациях. В том случае, если планируется на одном и том же компьюте-
ре совместно запускать ASP и РНР, в силу вступают разнообразные ограничения.
Запуск РНР как CGI-интерпретатора
или модуля
Интерпретатор РНР можно запускать как модуль или как отдельный бинарный
исполняемый CGI-файл (Common Gateway Interface — Общий шлюзовой интерфейс).
В большинстве случаев модульный вариант выбирают из соображений увеличения
общей производительности. CGI-версия позволяет пользователям сервера Apache
запускать PHP-сценарии с различных страниц под разными идентификаторами.
В этом приложении мы в основном касаемся вопросов использования модульного
метода для запуска РНР.
Инсталляция Apache, РНР и MySQL
на Unix-машине
В зависимости от существующих потребностей, а также от вашего опыта эксплуа-
тации систем на базе Unix, вы можете отдать предпочтение либо установке готового
бинарного кода, либо компиляции исходных текстов всех упомянутых приложений.
Оба подхода характеризуются своими достоинствами и недостатками.
Бинарная инсталляция может отнять буквально несколько минут у профессионала
и, может быть, минут десять—пятнадцать у новичка. Тем не менее, в этом случае сле-
дует помнить, что инсталляция бинарных файлов рассчитана на довольно-таки стан-
дартные конфигурации, коих всего лишь несколько, причем настройки сделаны кем-
то другим, и вряд ли они полностью удовлетворят ваши потребности.
Инсталляция исходных кодов требует нескольких часов для выгрузки самих кодов,
собственно установки и конфигурирования, поэтому может вызывать определенные
затруднения у новичков. Однако такая инсталляция обеспечивает полный контроль
над полученной конфигурацией. В этом случае у вас есть возможность выбирать, ка-
кие конкретно модули и какие версии необходимо устанавливать, а также какие кон-
фигурационные параметры включать.
Инсталляция бинарных файлов
Большинство дистрибутивов Linux включают в себя предварительно сконфигури-
рованный Web-сервер Apache со встроенной поддержкой РНР. Что конкретно под-
держивается, зависит от выбранного дистрибутива и версии.
Одним из существенных недостатков инсталляции бинарных файлов является не-
возможность получить наиболее свежую версию той или иной программы. В зависимо-
сти от степени важности выявленных и исправленных ошибок, использование старых
версий может быть и не сопряжено с какой-то проблемой. Самый существенный мо-
Приложение А. Инсталляция РНР и MySQL
839
мент, о котором следует помнить, заключается в том, что вы не можете управлять
тем, с какими опциями была скомпилирована ваша программа.
Наиболее гибким и надежным путем является компиляция исходных кодов всех
требуемых программ. Это отнимает несколько больше времени, нежели инсталляция
RPM-модулей, так что многие решают использовать доступные RPM-модули или дру-
гие бинарные пакеты. Даже если на официальных сайтах отсутствуют бинарные фай-
лы для вашей конфигурации, их вполне можно найти на каком-то неофициальном
сайте, воспользовавшись поисковой системой.
Инсталляция исходных кодов
Давайте теперь займемся установкой Apache, РНР и MySQL в среде Unix. Вначале
необходимо решить, какие дополнительные модули могут потребоваться вместе с
нашим трио. Поскольку некоторые из примеров в этой книге используют защищен-
ный сервер для Web-транзакций, потребуется установить сервер с поддержкой про-
токола SSL.
Наша конфигурация РНР будет более-менее совпадать с настройкой по умолча-
нию, но будет также рассказано и о подключении двух дополнительных библиотек
для РНР:
gd2
PDFlib
Это лишь две из великого множества доступных PHP-библиотек. На примере их
подключения можно разобраться, как вообще добавлять дополнительные библиотеки
в РНР. Компиляция большинства Unix-программ выполняется по очень похожему
сценарию.
Как правило, после инсталляции любой новой библиотеки необходимо повторно
компилировать РНР, поэтому, если заранее известно, что вы хотите получить, имеет
смысл сначала инсталлировать все необходимые библиотеки, а затем один раз пере-
компилировать РНР.
Здесь мы опишем процесс инсталляции на сервере, функционирующем под управ-
лением SuSE Linux, однако сам процесс является настолько общим, что его можно
применять в отношении любого L nix-сервера.
Давайте сначала посмотрим, какие файлы требуются для инсталляции:
Apache (http: //httpci.apache. org/) — Web-сервер.
OpenSSL (http://www.openssl.org/) — инструментальный набор разработчи-
ка с открытым исходным кодом, который необходим для реализации слоя за-
щищенных сокетов.
Mod_SSL (http: //www.modssl .org/) - Apache-модуль интерфейса кOpenSSL.
MySQL (http: / /www.mysql. com/) — система управления реляционными базами
данных.
РНР (http: / / www. php. net /) — серверный язык написания сценариев.
http://www.pdflib.com/pdflib/download/index.html — библиотека для гене-
рации PDF-документов на лету.
840
Часть VI. Приложения
ftp://ftp.uu.net/graphics/jpeg/ — Библиотека JPEG, необходимая для биб-
лиотек PDFlib и gd.
http://www.libpng.org/pub/png/libpng.html — Библиотека PNG, необходи-
мая для библиотеки gd.
http://www.gzip.org/zlib/ — Библиотека zlib, необходимая для библиотеки
PNG.
http://www.libtiff.org/ — Библиотека TIFF library, необходимая для биб-
лиотеки PDFlib.
f tp: / / f tp. cac. Washington. edu/imap/ — G-клиент IMAP, необходимый для IMAP.
Если вы планируете использовать функцию mail (), у вас должен быть установлен
агент передачи почты (mail transfer agent — МТА), хотя его установку мы здесь не рас-
сматриваем.
Мы исходим из предположения, что вы имеете привилегированный (root) доступ
к серверу, а в системе установлены следующие инструменты:
gzip или gunzip
gcc и GNU make
Когда все готово к инсталляции, следует загрузить все tar-файлы с исходными ко-
дами во временный каталог. Перед этим убедитесь, что в соответствующем разделе
жесткого диска имеется достаточно свободного пространства. В нашем случае вре-
менным каталогом является /usr/src. Во избежание проблем с правами доступа, все
файлы необходимо загружать, войдя в систему как пользователь root.
Инсталляция MySQL
В этом разделе мы рассмотрим, как выполняется инсталляция бинарных файлов
MySQL. Данная инсталляция автоматически помещает файлы в различные каталоги.
Для установки остальных членов нашего трио были выбраны следующие каталоги:
/usr/local/apache
/usr/local/ssl
Разумеется, можно использовать и другие каталоги.
Что ж, приступим! Прежде всего, с помощью команды su станьте пользователем
root:
К su root
Введите пароль для пользователя root. Затем перейдите в каталог, содержащий
файлы с исходным кодом, например:
# cd /usr/src
Рекомендации по MySQL гласят, чтобы пользователи загружали бинарный файл
MySQL, а не компилировали исходные коды с нуля. Какую версию выбрать — зависит
от необходимой вам функциональности. На момент написания оригинальной версии
этой книги производственной версией была MySQL 4.0. Для того чтобы можно было
пользоваться подзапросами, необходима версия MySQL 4.1, а для получения доступа к
механизму хранимых процедур — версия MySQL 5.0. На момент подготовки русскоя-
зычной редакции книги доступной была бета-версия MySQL 5.0.7, а также производ-
Приложение А. Инсталляция РНР и MySQL
841
ственная версия MySQL 4.1. Несмотря на то что предварительные версии MySQL от-
личаются довольно высокой стабильностью, возможно, по ряду причин вы не захо-
тите их использовать на собственном коммерческом сайте. Если вы хорошо знаете
возможности машины, то выбор версии MySQL не составит большого труда.
Потребуется загрузить следующие пакеты:
MySQL-server-VERSION.i386.rpm
MySQL-Max-VERSION.i3 86.rpm
MySQL-client-VERSION.i386.rpm
(Вместо заполнителя VERSION должен быть подставлен номер версии. Какая бы
версия вас не интересовала, убедитесь, что вы загружаете согласованный набор паке-
тов.) Если вы планируете выполнять на одной и той же машине и MySQL-клиент, и
MySQL-сервер, а также встроить поддержку MySQL в РНР, вам потребуются все пере-
численные выше пакеты.
Следующая команда обеспечивает инсталляцию серверов и клиента MySQL:
rpm -i MySQL-server-VERSION.i386.rpm
rpm -i MySQL-Max-VERSION.i386.rpm
rpm -I MySQL-client-VERSION.i386.rpm
После этого сервер MySQL должен установиться и запуститься.
Сейчас самое время установить пароль для пользователя root. Не забудьте заме-
нить строку new-pas sword в приведенной ниже команде на что-нибудь приемлемое,
иначе значением new-pas sword будет текущий пароль пользователя root.
mysqladmin -u root password 'new-password'
После инсталляции MySQL автоматически создаются две базы данных. Одна из
них, mysql, содержит таблицы, которые управляют пользователями, хостами и пра-
вами доступа к базам данных на реальном сервере. Вторая является тестовой базой
данных (test). Проверить это можно с помощью следующей командной строки:
# mysql -u root —р
Enter password:
mysql> show databases;
+-------------------+
| Database |
I mysql |
| test I
+-------------------+
2 rows in set (0.00 sec)
# mysql -u root -p
Введите пароль:
mysql> show databases;
| База данных
| mysql
| test
2 строки в наборе (0.00 с)
842
Часть VI. Приложения
Затем введите quit или \q для завершения MySQL-клиента.
Конфигурация MySQL по умолчанию разрешает входить в систему любому поль-
зователю без указания имени пользователя и пароля. Совершенно очевидно, что это
неприемлемо.
Последней порцией работы по установке MySQL является удаление анонимного
пользователя. Введите в командной строке следующие команды:
# mysql -u root -р
mysql> use mysql
mysql> delete from user where User='';
mysql> quit
Для того чтобы изменения вступили в силу, потребуется также выдать следующую
команду:
mysqladmin -u root -р reload
Необходимо также включить бинарную регистрацию на сервере MySQL, посколь-
ку она пригодиться для целей репликации. Для этого сначала остановите сервер:
mysqladmin -u root -р shutdown
Создайте файл с именем /etc/my.cnf, который будет служить в качестве файла
опций MySQL. На данный момент вам нужна только одна опция, хотя опций сущест-
вует великое множество. Полный список опций можно найти в руководстве по
MySQL.
Откройте файл /etc/my.cnf в каком-нибудь текстовом редакторе и поместите в
него следующие строки:
[mysqld]
log-bin
Сохраните файл и выйдите из редактора. Перезапустите сервер, выполнив
mysqld_safe.
Инсталляция PDFlib
Если вы решили не пользоваться библиотекой PDFlib для генерации PDF-файлов,
как упоминалось в главе 32, можете пропустить этот раздел.
Загрузите библиотеку PDFlib из
http://www.pdflib.com/products/pdflib/download/index.html
Для распаковки содержимого архива PDFlib введи те следующую команду:
# gunzip -с PDFlib-6.0.0pl-Linux.tar.gz | tar xvf -
Мы не будем использовать библиотеку PDFlib напрямую, так что вернемся к ней
позже, когда заработает РНР.
Инсталляция РНР
Вы снова должны иметь права пользователя root (если это не так, воспользуйтесь
командой su).
Перед инсталляцией РНР должен быть доступен сконфигурированный сервер
Apache, чтобы местонахождение всех элементов было известным. (Мы вернемся к
Приложение А. Инсталляция РНР и MySQL
843
этому вопросу позже, в разделе, посвященном настройке сервера Apache.) Перейдите
обратно в каталог, содержащий файлы с исходным кодом:
# cd /usr/src
# gunzip -с apache_l.3.31.tar.gz | tar xvf -
# cd apache_1.3.31
# ./configure --prefix=/usr/local/apache
Итак, можно приступить к установке РНР. Разархивируйте файлы с исходным ко-
дом РНР и перейдите в соответствующий каталог:
# cd /usr/src
# gunzip -с php-5.О.О.tar.gz | tar xvf -
# cd php-5.0.0
Опять-таки, утилита configure пакета PHP принимает множество опций. Вос-
пользуйтесь командой ./configure --help | less, чтобы определиться насчет до-
бавляемых опций. В данном случае мы хотим добавить поддержку' MySQL, Apache,
PDFlib и gd.
Обратите внимание, что все приведенные ниже строки относятся к одной коман-
де. Ее можно разместить в одной строке или, как показано ниже, в нескольких, ис-
пользуя символ продолжения (обратную косую черту (\)), что существенно улучшает
читабельность:
# ./configure --with-mysqli=mysql_config_path/mysql_config \
—with-apache=../apache_l.3.31 \
—with-jpeg-dir=/nyTb/K/jpeglib \
—with-tiff-dir=/nyrb/K/tiffdir \
—with-zlib-dir=/nyTb/K/zlib \
--with-imap=/nyTb/K/imapcclient \
--with-gd
Далее следует выполнить компоновку- и установку бинарных файлов:
# make
# make install
Скопируйте конфигурационный INI-файл в каталог lib:
# ср php.ini-dist /usr/local/lib/php.ini
или
# ср php.ini-recommended /usr/local/lib/php.ini
Две версии INI-файла, присутствующие в приведенных выше командах, содержат
различные наборы опций. Первый файл, php.ini-dist, предназначен для машин,
ориентированных на разработку программного обеспечения. Например, параметр
display errors в нем установлен равным On. Это существенно упрощает процесс
разработки, однако совершенно неприемлемо для машин с промышленными версия-
ми. Всегда, когда мы ссылались на значения по умолчанию для параметров php. ini,
мы имели в виду именно эту версию файла. Вторая версия, php. ini-recommended,
ориентирована на использование на машинах с промышленными версиями.
Для настройки опций РНР можно отредактировать файл php.ini. Несмотря на то
что в файле присутствует множество опций, лишь некоторым из них следует уде-
844
Часть VI. Приложения
лять внимание. Возможно, вы решите установить значение опции sendmail_path,
если планируете отправлять электронную почту из сценариев.
Теперь следует настроить OpenSSL, если вы хотите использовать и создавать вре-
менные сертификаты и CSR-файлы. В опции --prefix должен быть указан главный
каталог инсталляции:
# gunzip -с openssl-О.9.7d.tar.gz | tar xvf -
# cd openssl-0.9.7d
# ./config --prefix=/usr/local/ssl
А сейчас скомпонуйте ее, протестируйте и установите:
# make
# make test
# make install
Модуль mod_SSL должен быть сконфигурирован как загружаемый модуль Apache:
# cd /usr'src/
# gunzip -c mod_ssl-2.8.18-1.3.31.tar.gz | tar xvf -
# cd mod_ssl-2.8.18-1.3.31
# ./configure --with-apache=../apache_l.3.31
Обратите внимание, что к исходному дереву Apache можно добавлять модули. До-
полнительная опция —enable-shared=ssl позволяет выполнить сборку mod_SSL как
динамического совместно используемого объекта (dynamic shared object — DSO)
libssl.so. Более подробная информация о поддержке DSO сервером Apache содер-
жится в документах INSTALL и htdocs/manual/dso.html в дереве исходного кода
Apache. Поставщикам Internet-услуг и разработчикам, поддерживающим пакет, реко-
мендуется использовать DSO для достижения максимальной гибкости rnod_SSL. Од-
нако, следует отметить, что Apache поддерживает DSO далеко не на всех платформах.
# cd ../apache_l.3.31
# SSL_BASE=../openssl-О.9.7d \
./configure \
--enable-module=ssl \
--activate-module=src/modules/php5/libphp5.a \
--prefix=/usr/local/apache \
— enable-shared=s s1
В заключение можно выполнить сборку сервера Apache и сертификатов, а затем
установить их:
# make
Если все прошло успешно, на экран выводится следующее сообщение:
| Before you install the package you now should prepare the SSL |
I certificate system by running the 'make certificate' command. |
| For different situations the following variants are provided: |
| % make certificate TYPE=dummy (dummy self-signed Snake Oil cert)
| % make certificate TYPE=test (test cert signed by Snake Oil CA)
| % make certificate TYPE=custom (custom cert signed by own CA)
i % make certificate TYPE=existing (existing cert)
1 CRT=/path/to/your.crt [KEY =/path/to/your.key]
Приложение А. Инсталляция PHP и MySQL
845
Use TYPE=dummy when you're a vendor package maintainer,
the TYPE=test when you're an admin but want to do tests only,
the TYPE=custom when you're an admin willing to run a real server
and TYPE=existing when you're an admin who upgrades a server.
i (The default is TYPE=test)
I
| Additionally add ALGO=RSA (default) or ALGO=DSA to select
| the signature algorithm used for the generated certificate.
I
| Use 'make certificate VIEW=1' to display the generated data.
I
j Thanks for using Apache & mod_ssl. Ralf S. Engelschall
| rse@engelschall.com
| wwvnengelschall.com
| Перед инсталляцией пакета вы должны подготовить систему
| сертификатов SSL, выполнив команду 'make certificate'.
| Для различных ситуаций предлагаются следующие варианты:
I
| % make certificate TYPE=dummy
(фиктивный самостоятельно подписанный сертификат Snake Oil)
| % make certificate TYPE=test
(тестовый сертификат, подписанный агентством Snake Oil) [
| % make certificate TYPE=custom
(пользовательский сертификат, подписанный собственным агентством) |
| % make certificate TYPE=existing (существующий сертификат) |
| СЕТ=/путь/к/вашему.сертификату [КЕУ=/путь/к/вашему.ключу] |
I I
| Use TYPE=dummy если вы поддерживаете собственный пакет, |
| the TYPE=test если вы администратор,но хотите только выполнить тестирование, |
| the TYPE=custom если вы администратор и желаете запустить реальный сервер, |
| and TYPE=existing если вы администратор и желаете модернизировать сервер. |
| (Значением по умолчанию является TYPE=test) |
| Дополнительно установите ALGO=RSA (по умолчанию) или ALGO=DSA для выбора I
| алгоритма получения сигнатуры, который используется при генерации сертификатов.
I I
| Для вывода сгенерированных данных используйте 'make certificate VIEW=1'. |
| Спасибо за использование Apache и mod_ssl. Ральф С. Енгельшалл
| rse@engelschall.com
| wwvj.engelschall.com )
После этого можно создать пользовательский сертификат. В этом случае будет
выдан запрос на расположение, наименование компании и другую информацию.
В качестве контактной информации имеет смысл указывать реальные данные. На
другие выдаваемые вопросы вполне можно ограничиться ответами, предлагаемыми
по умолчанию.
# make certificate TYPE=custom
846
Часть VI. Приложения
Сейчас выполним установку Apache:
# make install
Если все прошло успешно, на экране должно появиться приблизительно такое со-
общение:
| You now have successfully built and installed the |
| Apache 1.3 HTTP server. To verify that Apache actually |
| works correctly you now should first check the |
| (initially created or preserved) configuration files |
| /usr/local/apache/conf/httpd.conf |
I I
| and then you should be able to immediately fire up |
| Apache the first time by running: |
i I
i /usr/local/apache/bin/apachectl start |
| Or when you want to run it with SSL enabled use: |
| /usr/local/apache/bin/apachectl startssl |
I I
| Thanks for using Apache. The Apache Group |
| http://www.apache.org/ |
| Вы успешно собрали и установили HTTP-сервер |
| Apache 1.3. Для того чтобы убедиться, что Apache работает |
| корректно, вы должны сначала проверить существование |
| конфигурационных файлов (вновь созданных или сохраненных) |
| /usr/local/apache/conf/httpd.conf |
I Затем можно будет непосредственно запустить |
| Apache первый раз, набрав команду: |
| /usr/local/apache/bin/apachectl start |
| Если вы хотите запустить его с поддержкой SSL, введите команду: |
i |
| /usr/local/apache/bin/apachectl startssl |
I Спасибо за использование Apache. Группа Apache |
| http://www.apache.org/ |
Теперь самое время проверить работоспособность Apache и РНР. Однако до этого сле-
дует внести изменения в файл httpd. conf, добавив в конфигурацию тип РНР.
Фрагменты файла httpd.conf
Внимательно просмотрите файл httpd. conf. После выполнения всех предыдущих
шагов файл httpd. conf должен находиться в каталоге /usr/local/apache/conf.
Приложение А. Инсталляция РНР и MySQL
847
В этом файле присутствует опция AddType для РНР, однако она закомментирова-
на. Вы должны убрать символы комментария, как показано ниже:
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
Сейчас все готово к запуску и проверке работоспособности сервера Apache. Вна-
чале сервер следует запустить без поддержки SSL, дабы просто убедиться, функцио-
нирует ли он. После этого необходимо проверить поддержку’ РНР, остановить и пе-
резапустить сервер, но уже с поддержкой SSL, чтобы протестировать и ее.
С помощью утилиты configtest выполняется проверка правильности конфигура-
ции:
# cd /usr/local/apache/bin
# ,/apachectl configtest
Syntax OK
# ./apachectl start
,/apachectl start: httpd started
Если сервер функционирует нормально, то при соединении с сервером в Web-
браузере выводится содержимое, подобное показанному на рис. А. 1.
Рис. А.1. Стандартная тестовая страница сервера Apache
Примечание
Подключиться к серверу можно с использованием имени домена или IP-адреса машины. Во
время тестирования следует испробовать оба варианта.
Работает ли поддержка РНР?
Теперь необходимо проверить поддержку РНР. Для этого потребуется соз-
дать файл test.php, содержащий код. который показан ниже. Файл должен быть
848
Часть VI Приложения
расположен в корневом каталоге документов — по умолчанию это /usr/local/
apache htdocs. Обратите внимание, что в общем случае путь зависит от выбранного
в начале каталога. В процессе работы настройку можно изменить в файле
httpd.conf.
<?php phpinfoO; ?>
Результирующий вывод должен выглядеть приблизительно так, как показано на
рис. А.2.
Рис. А.2. Функция phpinfo () выдает полезную информацию о
конфигурации
Работает ли SSL?
Теперь необходимо протестировать и SSL. Для этого потребуется остановить сер-
вер и перезапустить его с включенной поддержкой SSL:
# /usr/local/apache/bin/apachectl stop
# /usr/local/apache/bin/apachectl startssl
Затем следует подключиться к серверу с помощью Web-браузера и выбрать прото-
кол https:
https://yourserver.yourdomain.com
Следует проверить и подключение к серверу с использованием его IP-адреса:
https://ххх.ххх.ххх.ххх
Приложение А. Инсталляция РНР и MySQL
849
Если все работает, как должно быть, сервер отправит браузеру сертификат для ус-
тановки безопасного соединения. Браузер запросит подтверждение на принятие са-
моподписанного сертификата. Если сертификат поступил от какого-то центра по вы-
даче сертификатов, которому браузер уже доверяет, никаких сообщений выдаваться
не будет. В нашем примере мы создали и подписали собственные сертификаты. Сей-
час мы не склонны платить за сертификат кому бы то ни было, поскольку просто хо-
тим проверить, все ли работает как надо.
Если вы используете Internet Explorer или Mozilla, то в строке состояния появится
символ замка. Он говорит о том, что безопасное соединение успешно установлено.
Пиктограмма, используемая в браузере Mozilla, показана на рис. А.З.
Рис. А.З. Web-браузеры выводят пиктограмму для от-
ражения того, что просматриваемая в текущий момент
страница поступила по безопасному соединению
Заключительные шаги
Если вы решили использовать разделяемый объект PDFlib. равно как и другие мо-
дули, установленные подобным образом, вам потребуется предпринять несколько
дополнительных шагов.
Во-первых, скопируйте файл libpdf_php (из подкаталога bind php;php-50x ката-
лога, в который извлекалась PDFlib) в каталог PHP-расширений extensions. Скорее
всего, этим каталогом будет:
/usr/local/lib/php/extensions
Добавьте в файл php. ini следующую строку:
extension = libpdf_php.so
Инсталляция Apache, РНР и MySQL
на Windows-машине
В среде Windows процесс установки несколько отличается, поскольку РНР можно
настроить либо как CGI-сценарий (php.exe), либо как ISAPI-модуль (php5isapi.dll).
Тем не менее, Apache и MySQL устанавливаются таким же образом, как и под Unix. До
инсталляции пакетов на Windows-машине следует убедиться, что уже установлены
последние версии обновлений служб операционной системы.
Как и ранее, начинать нужно с загрузки всех новейших файлов исходного кода во
временный каталог на диске, имеющем достаточный объем свободного пространства.
В нашем случае временным каталогом для установки будет с : \ temp download.
850
Часть VI. Приложения
Если вы имеете дело с медленным сетевым соединением, имеет смысл воспользо-
ваться версией на компакт-диске, однако вряд ли она окажется столь же новой, как на
официальном сайте.
Инсталляция MySQL под Windows
Приведенные ниже инструкции по инсталляции рассчитаны на работу под управ-
лением Windows ХР.
Начнем мы с настройки MySQL. Предполагая, что все необходимые файлы благо-
получно загружены, распакуйте zijt-файл MySQL во временный каталог и запустите на
выполнение программу Setup.exe. Эта программа — ни что иное как стандартный
мастер установки InstallShield, и должна выглядеть подобно большинству программ
такого рода.
Выбор варианта ‘Обычная” (“Typical Install”) в мастере приводит к тому, что зада-
ется лишь вопрос о том, куда необходимо проинсталлировать MySQL. По умолчанию
MySQL будет установлен в каталог с: \mysql. По прошествии процесса инсталляции
систему можно будет, при желании, переместить в другой каталог, однако в этом слу-
чае для восстановления ее работоспособности потребуется предпринять несколько
дополнительных шагов.
После перемещения MySQL, при запуске исполняемого файла mysqld ему потре-
буется передать несколько параметров командной строки, в которых должно быть
указано, что где находится. Для вывода всех существующих параметров служит сле-
дующая команда:
C:\mysql\bin\mysqld --help
Например, если система MySQL была перемещена в каталог ' D: \programs\mysql',
для запуска MySQI. вам потребуется выдать следующую команду:
D:\programs\mysql\bin\mysqld --basedir D:\programs\mysql
Если вы изменили местоположение системы MySQL, которая выполняется как
Windows-служба, вы должны будете создать INI-файл с именем my. ini и поместить его
в главный каталог Windows. В этом файле должны находиться такие строки:
[mysqld]
basedir=D: /programs/mysql/Ып/
datadir= D:/programs/mysql/data/
В случае установки под Windows NT/2000/ХР, именем MySQL-сервера будет mysqld-nt,
и, как правило, он должен быть установлен как служба. Служба — это программа, ко-
торая постоянно выполняется в фоновом режиме и предназначена для реализации
определенных услуг для других программ. Службы обычно запускаются автоматиче-
ски после запуска машины, что экономит время, необходимое для их ручного старта.
Инсталляция MySQI. в виде службы выполняется за счет выдачи следующих ко-
манд в режиме командной строки:
cd c:\mysql\bin
mysqld-nt -install
Ответ, который должен быть получен, выглядит так:
Service successfully installed.
Служба успешно установлена.
Приложение А. Инсталляция РНР и MySQL
851
Теперь можно запустить и остановить службу MySQL из командной строки сле-
дующим образом:
NET START mysql
NET STOP mysql
Как видите, имя исполняемого файла выглядит KaKmysqld-nt, а вот имя службы —
как просто mysql. После выдачи команды NET START mysql вы должны получить сле-
дующее сообщение:
The MySql service is starting.
The MySql service was started successfully.
Запускается служба MySql.
Служба MySql запушена успешно.
Как только сервер будет установлен, его можно запускать, останавливать или оп-
ределять для него автоматический запуск с использованием утилиты Службы
(Services), доступной в панели управления. Для запуска этой утилиты щелкните на
кнопке Пуск (Start), выберите в меню Настройка (Settings), а затем Панель управле-
ния (Control Panel). Дважды щелкните на пиктограмме Администрирование (Adminis-
trative Tools), а затем на пиктограмме Службы (Sendees).
Окно утилиты Службы показано на рис. А.4. Для настройки любого параметра
MySQL потребуется сначала остановить эту службу, а затем установить значение ее
параметра Тип запуска (Startup Туре) равным Авто (Auto). Службу MySQL можно ос-
тановить либо с помощью утилиты Службы, либо с использованием команд NET STOP
MySQL или mysqladmin shutdown.
Рис. A.4. Утилита “Службы” позволяет конфигурировать службы, вы-
полняемые на вашей машине
852
Часть VI. Приложения
Для проверки работоспособности MySQL воспользуйтесь следующей командой:
С:\mysql\bin\mysqlshow
Конфигурация, установленная по умолчанию, на самом деле идеальной не являет-
ся. Существует несколько аспектов, которым потребуется уделить внимание:
Установка переменной PATH.
Удаление анонимного пользователя.
Установка пароля для пользователя root.
Установка переменной PATH
MySQL поступает со множеством утилит командной строки, каждая из которых
характеризуется своей степенью удобства в использовании. Ни одну из них не удастся
просто так вызвать, если только имя каталога бинарных файлов MySQL не будет
включено в переменную окружения PATH. Назначение упомянутой переменной со-
стоит в том, что она указывает Windows, где искать исполняемые файлы.
Многие из используемых в командной строке Windows команд являются внутрен-
ними, и они встроены в командный интерпретатор cmd.exe. Другие команды, такие
как format или ipconfig, имеют свои собственные исполняемые файлы. Не стоит и
говорить, что необходимость ввода, например, С: \WINNT\system32\format, является
совершенно неприемлемой.
Точно так же неудобно было бы набирать С: \mysql\bin\mysql для запуска мони-
тора MySQL. Имена каталогов, в которых хранятся исполняемые файлы базовых
Windows-команд, подобные format.exe, автоматически помещаются в переменную
PATH, что позволяет в любом месте просто набирать так: format. Таким образом, дабы
достичь той же степени удобства и в отношении утилит MySQL, мы должны добавить
в переменную PATH соответствующую информацию.
Щелкните на кнопке Пуск, выберите в меню Настройка, а затем Панель управле-
ния. Дважды щелкните на элементе Система (System) и перейдите на вкладку Допол-
нительно (Advanced). Щелчок на кнопке Переменные среды (Environment Variables)
приводит к отображению диалогового окна, в котором можно просмотреть все пере-
менные окружения, определенные в системе. Дважды щелкните на переменной PATH
и отредактируйте ее значение.
Добавьте точку с запятой в конце текущего значения и добавьте c:\mysql\bin.
После щелчка на кнопке ОК новое значение PATH будет сохранено в системном рее-
стре. Изменения вступят в силу после перезагрузки машины.
Удаление анонимного пользователя
Конфигурация MySQL, устанавливаемая по умолчанию, дает возможность любому
пользователю получить доступ в систему', не вводя ни имени пользователя, ни тем
более пароля. Очевидно, что подобное совершенно неприемлемо.
Одной из первых вещей, которые потребуется проделать, является удаление ано-
нимного пользователя. Для этого откройте окно командного интерпретатора и вве-
дите следующие команды:
c:\mysql\bin\mysql -u root
use mysql
delete from user where User=’
Приложение А. Инсталляция PHP и MySQL
853
quit
c:\mysql\bin\mysqladmin -u root reload
Теперь анонимный пользователь больше не присутствует в нашей конфигурации.
Установка пароля пользователя root
Даже учетная запись суперпользователя root сейчас не имеет пароля. Для установ-
ки пароля для такого пользователя введите следующие команды:
c:\mysql\bin\mysqladmin -u root password ваш_пароль
c:\mysql\bin\mysqladmin -и root -h имя_вашего_хоста password ваш_пароль
Теперь вы должны заметить, что команды, которые ранее не требовали ввода
комбинации имя пользователя/пароль, теперь не выполняются. Попытка выполнить
команду:
c:\mysql\bin\mysqladmin reload
или
c:\mysql\bin\mysqladmin shutdown
теперь терпит неудачу.
Начиная с этого момента, вам потребуется с помощью флага -и указывать имя
пользователя, а с помощью флага -р — пароль, например:
c:\mysql\bin\mysqladmin -u root -р reload
После этого MySQL предложит вам ввести пароль для пользователя root, который
вы только что установили.
За более подробной информацией обращайтесь на Web-сайт MySQL, доступный
по адресу:
http://www.mysql.com
А сейчас все готово к инсталляции Apache под Windows. Так давайте и начнем!
Инсталляция Apache под Windows
Сервер Apache 1.3 и последующие версии разработан так. чтобы выполняться под
управлением операционных систем Windows NT, 2000 и ХР. Программа установки
работает только на семействе процессоров х86, таких как произведенные компанией
Intel. Сервер Apache также может выполняться под Windows 95 и 98. Во всех случаях в
системе должен быть установлен протокол TCP/IP. Под Windows 95 и 98 следует убе-
диться в наличии библиотеки Winsock 2.
Зайдите на сайт http://httpd.apache.org и загрузите бинарный файл версии
Apache 1.3 для Windows (версия Apache 2.0 является многопоточной, но поскольку
некоторые внешние библиотеки РНР не безопасны в отношении потоков, мы реко-
мендуем пользоваться версией Apache 1.3).
Мы загрузили файл apache_l. 3.31-win32-x86-no_src .msi. Он представляет со-
бой MSI-архив, содержащий текущую версию (в рамках иерархии выпуска 1.3) для
Windows без исходного кода. MSI-файл — это формат, который использует мастер ус-
тановки Windows.
854
Часть VI. Приложения
Вопросы компиляции исходного кода Apache возникать не должны, поскольку по-
добное требуется только в случаях, например, когда постоянно появляется неустра-
нимая ошибка, или же вы решили присоединиться к команде разработчиков Apache.
Упомянутый выше единственный файл содержит в себе весь сервер Apache, готовый
к установке.
Выполните двойной щелчок на этом файле, чтобы начать процесс инсталляции.
Процесс инсталляции должен выглядеть очень знакомым. Как показано на рис. А.5,
он выполняется аналогично установке других продуктов, при которой используется
стандартный мастер установки Windows.
Рис. А.5. Программа установки Apache проста в ис-
пользовании
Программа установки запросит у вас следующую информацию:
Имя сети, имя сервера и адрес электронной почты администратора. Если вы
задумали построить сервер для реального использования, вы должны знать
четкие ответы на эти вопросы. В случае если вы строите сервер для личного
использования, ответы на вопросы не настолько важны.
Хотите ли вы запускать Apache как службу. Как и в случае с MySQL, на этот во-
прос лучше ответить утвердительно.
Тип инсталляции. Мы рекомендуем отдать предпочтение варианту “Полная”
(“Complete”), тем не менее, вы можете выбрать и вариант “Выборочная” (“Cus-
tom”), если, например, решили не устанавливать некоторые компоненты, ска-
жем, документацию.
Каталог, в который необходимо установить Apache. (По умолчанию это
C:\Program Files\Apache Group \ Apache.)
После ответа на все перечисленные вопросы сервер Apache будет установлен и
запущен.
После запуска Apache будет прослушивать порт 80 (если только вы не измените ус-
тановки Port, Listen или BindAddress в конфигурационном файле). Для подключе-
Приложение А. Инсталляция РНР и MySQL
855
ния к серверу и доступа к странице по умолчанию запустите браузер и введите сле-
дующий URL-адрес:
http://localhost/
В результате в браузер должна загрузиться страница приглашения, похожая на по-
казанную на рис. А.1, а также ссылка на учебник по Apache. Если вообще ничего не
произошло или же вы получили сообщение об ошибке, просмотрите файл error. log,
расположенный в каталоге logs. Если вы не подключены к Internet, возможно, вам
потребуется использовать следующий URL-адрес:
http://127.0.0.1/
Это IP-адрес, который означает localhost.
Если номер прослушиваемого порта был изменен на что-то, отличное от 80, в ко-
нец URL-адреса необходимо добавить : номер_порта.
Не следует забывать, что сервер Apache не допускает совместного использования
одного и того же порта с другим ТСР/1Р-приложением.
Запуск и останов Apache выполняется через меню Пуск — Apache просто добавляет
свои файлы в группу Программы1^ Apache HTTP Server (Prorgams^Apache HTTP
Server). В подгруппе Control Apache Server (Управление сервером Apache) можно
найти опции запуска, останова и перезапуска сервера.
После инсталляции Apache может возникнуть необходимость отредактировать
конфигурационные файлы, которые находятся в каталоге conf. Мы коснемся вопро-
сов редактирования конфигурационного файла httpd.conf, когда дойдем до инстал-
ляции РНР.
Если вам необходима версия Apache с поддержкой SSL под Windows, просмотрите
исключительно полезные списки часто задаваемых вопросов и ответов, доступные по
адресу:
http: //tud.at/prograntm/apache-ssl-Win32-howto.php3
Однако учтите, что эти списки вопросов/ответов — явно не для слабонервных.
Инсталляция РНР под Windows
Чтобы установить РНР под Windows, начните с загрузки РНР5 из сайта
http://www.php.net.
Для инсталляции под Windows потребуется загрузить два файла — zip-архив, со-
держащий РНР (с именем наподобие php-5-0.0-Win32.zip), и zip-архив с кол-
лекцией библиотек (с именем pecl-5.0.0-Win32 . zip или похожим).
Начните с распаковки zip-файла в любой рабочий каталог. Обычно это каталог
С: \РНР, и как раз на него мы и будем ссылаться во время дальнейших объяснений.
Установить библиотеки PECL можно путем распаковки соответствующего архива
в каталог расширений РНР. Если в качестве основного каталога для РНР выбран
С: \РНР, то расширения, как правило, хранятся в С: \PHP\ext.
Теперь выполните описанные ниже действия.
1. В главном каталоге должны присутствовать файлы php. ехе и php5ts .dll. Они
необходимы для запуска РНР как CGI-модуля. Если вам необходимо запускать
РНР как SAPI-модуль, вы должны перейти в каталог С: \PHP\sapi и скопировать
856
Часть VI. Приложения
из него соответствующую DLL-библиотеку в каталог C:\PHP. Например, для
случая сервера Apache такой библиотекой будет php5apache.dll.
SAPI-модули отличаются большим быстродействием и простотой защиты. CGI-
версия позволяет запускать РНР из командной строки. Опять-таки, выбор ис-
ключительно за вами.
2. Скопируйте все DLL-библиотеки из каталога С: \PHP\dlls в системный каталог
Windows. В случае Windows NT или 2000 таким каталогом будет C:\winnt\
system32, а в случае Windows ХР — С: \windows\system32.
3. Настройте конфигурационный файл php.ini. В состав дистрибутива РНР
входят два конфигурационных файла: php.ini-dist и php. ini-recommended.
Мы предполагаем, что вы будете использовать php.ini-dist во время изу-
чения РНР и на инструментальном сервере, a php. ini-гecommended — на
производственном сервере. Скопируйте этот файл и переименуйте копию на
php.ini. Поместите полученный файл в каталог 1 %SYSTEMROOT% '. Обычно это
каталог с : \winnt или с: \winnt40 для Windows NT или 2000, и с: \windows для
Windows ХР.
4. Отредактируйте файл php. ini. В нем содержится изрядное количество кон-
фигурационных параметров, большинство из которых на данный момент
можно проигнорировать. Единственными настройками, которые придется
изменить сейчас, являются:
• Директива extension_dir должна указывать на каталог, в котором разме-
щаются DLL-библиотеки расширений. При стандартной инсталляции это
должен быть каталог C:\PHP\ext. Таким образом, ваша копия файла
php. ini должна содержать строку:
extension_dir = c:/php/ext
• Директива doc_root должна указывать на корневой каталог, который об-
служивается вашим Web-сервером. Для случая сервера Apache строка долж-
на выглядеть следующим образом:
doc_root = "с:/Program Files/Apache Group/Apache/htdocs"
а для случая сервера IIS — вот так:
doc_root = "с:/Inetpub/wwwroot"
• Вам потребуется также определить, какие расширения необходимо запус-
тить. Мы предполагаем, что на данном этапе вас должен интересовать толь-
ко РНР, однако вы можете добавить любые требуемые расширения. Для то-
го чтобы добавить расширения, посмотрите на список, помеченный как
Windows Extensions. Он содержит множество строк наподобие:
;extension=php_pdf.dll
Чтобы включить данное расширение, удалите символ точки с запятой в на-
чале строки (понятно, что помещение символа точки с запятой в начало
строки, наоборот, отключает расширение). Изменения вступают в силу
только после повторного запуска Web-сервера.
Приложение А. Инсталляция РНР и MySQL
857
Для целей этой книги потребуется включить следующие расширения:
php_pdf.dll, php_gd2.dll, php_imap.dll и php_mysqli.dll. Удалите сим-
волы комментария с соответствующих строк. Кроме того, вы заметите, что
строка для расширения php_mysqli . dll отсутствует. Добавьте ее:
extension=php_mysqli.dll
Сохраните и закройте файл php .ini.
5. Если вы используете файловую систему NTFS, убедитесь, что пользователь, от
имени которого запускается Web-сервер, обладает полномочиями по чтению
файлаphp. ini.
Добавление РНР в конфигурацию сервера Apache
Вам может потребоваться отредактировать один из множества конфигурационных
файлов Apache. Откройте файл httpd.conf в одном из редакторов. Обычно упомяну-
тый файл расположен в каталоге C:\Program Files\Apache Group\Apache\conf\.
Найдите в нем следующие строки:
LoadModule php5_module с:/php/php5apache.dll
AddModulе mod_php5.с
AddType application/x-httpd-php .php
Action application/x-httpd-php ’’/php/php.exe"
Если эти строки в файле отсутствуют, добавьте их, сохраните файл и перезапусти-
те сервер Apache.
Добавление РНР и MySQL в Microsoft IIS и PWS
Этот раздел посвящен добавлению поддержки РНР и MvSQL в сервер IIS в виде
ISAPI-модуля (php5isapi.dll). (Конечно, вы можете настроить РНР как CGI-модуль,
однако мы настоятельно рекомендуем отдать предпочтение именно ISAPI-модулю,
поскольку такой вариант характеризуется более высоким быстродействием.) Пред-
полагается, что к этом}’ моменту вы благополучно выполнили шаги, описанные в пре-
дыдущих разделах. Главное отличие состоит в том, что директива конфигурации
doc_root, скорее всего, должна быть равна “с : /Inetpub/wwwroot”.
Сейчас потребуется открыть консоль Internet Information Services (Информаци-
онные службы Internet). В среде Windows 2000 или ХР она достойна через пиктограм-
му Администрирование (Administrative Tools) панели управления. Если вы не находи-
те пиктограмму Internet Information Services, значит, вам необходимо остановить IIS
из дистрибутивного компакт-диска используемой операционной системы.
Сразу же после открытия консоли слева вы должны увидеть древовидное пред-
ставление всех служб. Щелкните правой кнопкой мыши на элементе, представляю-
щем ваш Web-сервер (обычно он называется Веб-узел по умолчанию (Default Web
Server)) и выберите в контекстном меню пункт Свойства (Properties).
На экране должно появиться диалоговое окно Свойства (Properties), в котором
среди всего множества информации потребуется изменить только несколько пара-
метров. Щелкните на кнопке Настройка (Configuration), которая расположена на
вкладке Домашний каталог (Home Directory). Под списком Сопоставление приложе-
ний (Application Mappings) щелкните на кнопке Добавить (Add), чтобы добавить
РНР. В появившемся диалоговом окне введите полный путь к библиотеке
858
Часть VI. Приложения
php5isapi.dll (что-то похожее на c:\php\php5isapi.dll). В поле Расширение
(Extension) введите .php. Кроме того, должен быть отмечен флажок Обработчик сце-
нариев (Script Engine). Щелкните на кнопке ОК.
Если вам требуется выполнять HTTP-аутентификацию (которая подробно рас-
сматривалась в этой книге), перейдите на вкладку Фильтры ISAPI (ISAPI Filters) диа-
логового окна свойств. Щелкните на кнопке Добавить. В появившемся диалоговом
окне в качестве имени фильтра введите РНР, а в качестве исполняемого файла укажи-
те полный путь к библиотеке php5isapi . dll, который уже вводился ранее. Щелкните
на кнопке ОК. Закройте диалоговое окно свойств, щелкнув на кнопке Применить
(Apply).
Сейчас потребуется остановить сервер (если это не было сделано ранее) и по-
вторно запустить его. Это делается в окне консоли Internet Information Services.
Щелкните правой кнопкой мыши на требуемом Web-сервере и выберите в контекст-
ном меню пункт Остановить (Stop). Аналогично выполняется запуск, но только в этом
случае должен быть выбран пункт Пуск (Stall).
Убедитесь, что все работает
После запуска сервера Apache следует убедиться, что РНР работает корректно.
Для этого потребуется создать сценарий rest .php с одной-единственной строкой:
<? phpinfoO; ?>
Файл должен быть помещен в корневой каталог документов (обычно это
C:\Program File\Apache Group\Apache\htdocs для Apache и C:\Inetpub\wwwroot —
для IIS). Далее загрузите его в браузер, указав следующий URL-адрес;
http://localhost/test.php
или:
http://ip-адрес-вашего-компьютера/test.php
Если в окне браузера выводится страница, похожая на показанную на рис. А.2,
значит, РНР функционирует нормально.
Инсталляция PEAR
В состав дистрибутива РНР5 входит программа установки пакетов репозитория
PHP-расширений и приложений (РНР Extension and Application Repositor}' — PEAR).
Если вы работаете в среде Windows, перейдите в режим командной строки и введите:
С:\php\go-pear
Сценарий go-pear задаст несколько простых вопросов, связанных с тем, куда
должны быть помещены программа установки и стандартные классы PEAR, после
чего сценарий загрузит и установит требуемые файлы.
На этом этапе вы должны иметь программу установки пакетов и базовые библио-
теки PEAR. Пакеты затем можно будет устанавливать с помощью следующей команды:
pear install package
где package должен заменяться реальным именем пакета, который требуется ус-
тановить.
Приложение А. Инсталляция РНР и MySQL
859
Для получения списка всех доступных пакетов служит такая команда:
pear list-all
Чтобы просмотреть, какие пакеты установлены, воспользуйтесь командой:
pear list
Для установки пакета Mail_Mime, речь о котором шла в главе 30. введите:
pear install Mail_Mime
Пакет DB, упомянутый в главе 11, устанавливается автоматически, тем не менее,
стоит убедиться, что вы располагаете наиболее свежей его версией. Для этого можно
ввести:
pear list-upgrades
Если окажется, что доступна более новая версия, нежели та, что установлена у вас,
воспользуйтесь следующей командой:
pear upgrade DB
Если описанная выше процедура не работает, мы рекомендуем выгрузить пакеты
PEAR вручную, воспользовавшись следующим URL-адресом:
http://pear.php.net/packages.php
Вы должны попасть на страницу со списком доступных пакетов. Например, при
решении ряда задач в этой книге мы пользовались пакетом Mail_Mime. Найдите на
странице ссылку на этот пакет и щелкните на “Download Latest” (“Загрузить послед-
нюю версию”) для загрузки копии. Полученный zip-файл потребуется распаковать и
поместить результат в какой-нибудь каталог, входящий в include__path.
Вы должны создать каталог с именем c:\php\pear или каким-нибудь подобным.
В случае загрузки пакетов вручную мы рекомендуем помещать каждый пакет в свой
подкаталог, расположенный внутри каталога c:\php\pear. Библиотека PEAR имеет
стандартную структуру, которая предполагает размещение отдельных пакетов в соб-
ственных подкаталогах (как это делает программа установки), поэтому мы рекомен-
дуем придерживаться этого соглашения. Например, пакет Mail_Mime относится к
разделу Mail и должен размещаться в каталоге с: \php\pear\Mail.
Настройка других конфигураций
Системы РНР и MySQL можно настроить на функционирование с другими Web-
серверами, такими как Omni, HTTPD и Netscape Enterprise Server. Мы не рассматри-
ваем эти вопросы в данном приложении, однако найти необходимую информацию
можно на официальных Web-сайтах MySQL и РНР, которые доступны, соответствен-
но, по следующим URL-адресам: http: / /www.mysql. com и http: //www.php.net.
860
Часть VI. Приложения
Б
Ресурсы в Web
В данном приложении приведены адреса некоторых из великого множества ре-
сурсов, доступных в Web, которые содержат обучающие системы, статьи, ново-
сти и примеры PHP-кода. Это лишь небольшая часть того, что можно найти в
Internet. Мы не можем привести все ссылки, так как печатный объем книги ограни-
чен. Более того, каждый день возникают все новые и новые сайты, посвященные РНР
и MySQL, поскольку число Web-разработчиков, использующих РНР и MySQL и иже с
ними, постоянно растет.
Ресурсы, посвященные РНР
РНР.Net — http://www.php.net — основной сайт, посвященный РНР. Из него
можно загрузить исполняемые файлы и исходный код РНР, справочное руково-
дство, архивы списков рассылки и последние новости, касающиеся РНР.
Zend.Com — http: / /www. zend.com — сайт механизма Zend, под управлением кото-
рого работает РНР. Этот портал содержит форумы, а также базу данных примеров
классов и кода, которые можно свободно использовать.
PEAR — http://pear.php.net — репозиторий PHP-расширений и приложений
(РНР Extension and Application Repository — PEAR). Это официальный сайт PHP-
расширений.
PECL — http://pecl.php.net — родственный сайт PEAR. Сайт PEAR предлагает
классы, написанные на РНР, а сайт PECL — расширения, реализованные на языке
С. Классы PECL иногда сложны в установке, однако они предлагают более широ-
кую функциональность и практически всегда мощнее своих аналогов на РНР.
PHPCommunity — http: I /www. phpeornmunity. org — новый сайт сообщества разра-
ботчиков на РНР.
php|architect — http: //www.phparch.com — журнал, посвященный РНР. Этот сайт
предлагает бесплатные статьи; на нем также можно подписаться на получение все-
го журнала в печатном или PDF-формате.
РНР Magazine — http: //www.phpmag.com — еще один журнал, посвященный РНР.
который также доступен в электронном и печатном виде.
PHPWizard.net — http://www.phpwizard.net — источник многих хороших РНР-
приложений, например, phpChat и phpIRC.
PHPMyAdmin.Net — http://www.phpmyadmin.net - домашний сайт популярного
PHP-интерфейса для MySQL.
PHPBuilder.com — http://www.phpbuilder.com — портал обучающих курсов по
РНР. На этом сайте можно найти ответы практически на любые вопросы. Кроме
того, поддерживается форум, в который можно посылать свои вопросы.
DevShed.com — http://www.devshed.com — портал, содержащий замечательные
обучающие руководства по РНР, MySQL, Perl и другим языкам программирования.
РХ-РНР Code Exchange — http: I /рх. sklar. com — хороший сайт, с которого стоит
начинать. Здесь много примеров сценариев и полезных функций. Сайт содержит
удобную поисковую систему.
The РНР Resource - http://www.php-resource.de — удобный источник обучаю-
щих руководств, статей и сценариев. Единственная “проблема” заключается в язы-
ке — сайт реализован на немецком. Для его просмотра можно воспользоваться
сайтом с системой перевода. Правда, чтение предлагаемых PHP-кодов не должно
составить труда.
WeberDev.com — http://www.WeberDev.com — известный ранее как Berber’s РНР
sample page, этот сайт вырос буквально из ничего в место для обучающих руко-
водств и примеров кода. Он предназначен для пользователей РНР и MySQL и по-
крывает вопросы, связанные с безопасностью и базами данных.
HotScripts.com — http: / /www. hotscripts . com — хороший набор сценариев, разби-
тый по категориям. Сайт содержит сценарии на таких языках, как РНР, ASP.NET и
Perl. На нем представлена замечательная коллекция PHP-сценариев. Сайт часто об-
новляется, поэтому мы рекомендуем его регулярно посещать тем, кто ищет приме-
ры сценариев.
РНР Base Library — http: / /phplib. sourceforge. net — сайт, который использует-
ся разработчиками крупных проектов на РНР. Он содержит объемную библиотеку
альтернативных средств для управления сеансами, а также инструменты создания
шаблонов и абстрактного слоя баз данных.
РНР Center — http://vww.php-center.de — еще один немецкоязычный портал,
содержащий обучающие руководства, сценарии, советы и многое другое.
РНР Homepage — http://www.php-homepage.de - очередной немецкоязычный
сайт, посвященный РНР. Содержит сценарии, статьи, новости и многое другое.
Имеется раздел быстрых ссылок.
PHPIndex.com — http: //vww.phpindex.com — удобный французский портал с ог-
ромным количеством материала, посвященного РНР. Он содержит новости, отве-
ты на часто задаваемые вопросы, статьи, вакансии рабочих мест и многое другое.
WebMonkey.com — http://www.webmonkey.com -- портал с большим количеством
Web-ресурсов, обучающих руководств, примеров кода и так далее. Сайт покрывает
вопросы разработки, программирования, интерфейса к базам данных, мультиме-
диа и множество других.
The РНР Club — http: I/wm.phpclub.net — сайт, содержащий множество ресурсов
для начинающих программистов на РНР: новости, обзоры книг, примеры кода, фо-
румы, ответы на часто задаваемые вопросы, а также руководства для начинающих.
862
Часть VI. Приложения
PHP Classes Repository — http: / /phpclasses .upperdesign. com — основной целью
этого сайта является распространение бесплатных классов, реализованных па
РНР. Если вы разрабатываете код или ваш проект требует создания классов, обя-
зательно посетите этот сайт. Он содержит удобную поисковую систему.
The РНР Resource Index — http: //php.resourceindex.com — портал сценариев,
классов и документации. Он удобно разбит по категориям, что сокращает время
поиска.
РНР Developer — http: / /www. phpdeveloper. org — еще один портал, посвященный
РНР, который содержит новости, статьи, обучающие руководства.
Evil Walrus — http: / /www. evilwalrus . com — хороший портал сценариев на РНР.
SourceForge — http://sourceforge.net — источник ресурсов с открытым исход-
ным кодом. SourceForge не только помогает найти требуемый код, но и предос-
тавляет доступ к CVS, спискам рассылки и машинам для разработчиков программ-
ного обеспечения с открытым исходным кодом.
Codewalkers — http://codewalkers.com/ — сайт, содержащий статьи, обзоры по
книгам, руководства и раздел РНР Contest, где благодаря своим знаниям можно
что-нибудь выиграть. Соревнования по кодам проводятся каждые две недели.
РНР Developer’s Network Unified Forums — http://forums.devnetwork.net/
index.php — содержит дискуссии по всем вопросам, так или иначе связанным с
РНР.
РНР Kitchen — http: / /www. phpkitchen. com/ — Статьи, новости и другие материа-
лы по РНР.
Postnuke — http://www.postnuke.com, — часто используемая система управления
содержимым на РНР.
РНР Application Tools — http:, /www.php-tools.de/ — набор полезных РНР-
классов.
Ресурсы, посвященные MySQL и SQL
Сайт MySQL — http://www.mysql.com — официальный Web-сайт, посвященный
MySQL. Содержит отличную документацию, поддержку и большой объем другой
информации. Рекомендуется всем, кто использует MySQL; особенно полезны раз-
дел сайта, ориентированный на разработчиков, и архив списков рассылки.
The SQL Course — http: / /sqlcourse. com — сайт, предлагающий вводный обучаю-
щий курс SQL с хорошо понятными инструкциями. Позволяет проверить изучен-
ный материал на встроенном интерпретаторе SQL. Расширенная версия находит-
ся по адресу http://www.sqlcourse2.com.
SearchDatabase.com — http://searchdatabase.com — хороший портал с обилием
полезной информации по базам данных. Он предлагает отличные обучающие ру-
ководства, советы, официальные издания, ответы на часто задаваемые вопросы,
обзоры и так далее. Рекомендуем посетить!
Приложение Б. Ресурсы в Web
863
Ресурсы, посвященные Apache
Apache Software — http: I /www.apache.org — сайт, с которого следует начать, если
вам нужны исходные коды или бинарные файлы сервера Apache. Сайт также со-
держит онлайновую документацию.
Apache Week — http: //www.apacheweek.com — электронный еженедельный журнал,
полезный для тех, кто работает с сервером Apache или использует его службы.
Apache Today — http://www.apachetoday.com — ежедневно обновляемый источ-
ник новостей и другой информации по Apache. Для отправки вопросов необходи-
мо предварительно зарегистрироваться.
Разработка Web-приложений
Philip arid Alex's Guide to Web Publishing (Путеводитель no Web-публикациям от
Филиппа и Алекса) — http://philip.greenspun.com/panda/ — остроумный путе-
водитель по методам разработки программного обеспечения для Web. Одна из не-
скольких книг по данной теме, подготовленная в соавторстве с Samoyed.
864
Часть VI Приложения
Предметный указатель
Apache, 163; 289; 296; 307; 349; 363; 364;
368; 369; 373; 382; 383; 385-393; 402;
421; 505; 519; 783; 788; 838-840; 843-
848; 850; 854-859; 864
mod_auth_mysql, 373; 390- 393
ASP.NET, 42; 862
С
CGI, 84: 398; 430; 838; 839; 850; 856-858
cookie-набор, 395; 484—486; 492
CSS, См. Каскадная таблица стилей, 160;
161; 510; 521
CVS, 518; 519; 525; 863
D
DDoS, См. Отказ в обслуживании распре-
деленный, 357; 358
DES, 365; 392
DNS, См. Служба имен доменов, 308; 438;
439
DoS, См. Отказ в обслуживании, 357; 358
F
FAT, 100
FTP, 85; 86; 89; 382; 399; 406; 414; 431; 432;
434; 439-447; 617; 621; 649; 769
ftp_connect(), 443
ftp_fget(), 445; 446
ftp_fput(), 446
ftp_get(), 446
ftp_mdtm(), 444
ftp_put(), 446
ftp_quit(), 443; 446
ftp_size(), 447
GIF, 459-462; 468; 788; 792
GNU, 231; 234; 264; 405; 408; 841
GPG, 405-411; 609
I
IDEA, 365
J
JavaScript, 40; 42; 373; 395; 396; 535; 547;
669
К
KPHPDevelop, 519
M
MD5, 367; 380; 382
МХ-запись, 439
О
ODBC, См. Открытый интерфейс взаи-
модействия с базами данных, 297; 300
Р
PDF, 52; 509; 760-762; 766-771; 774; 778-
786; 789-791; 793; 794; 840; 843; 861
PEAR, 227; 283; 297; 298; 439; 520; 685;
727; 729; 731; 838; 859-861
PECL, 459; 856; 861
PGP, 404-406; 408; 609
R
RAID, 356; 371
RC2, 365
RC4, 365
RC5, 365
REST, 794-796; 802; 803; 810; 811; 817;
821; 822; 824; 825
RFC1939, 432
RFC2060, 432
RFC2616, 432
RFC822, 131; 432; 450
s
SHA, 367; 380
show_source(), 506
SimpleXML, 795; 803
Smarty, 522
SOAP, 509; 794-796; 800-803; 810; 811;
817-822; 824; 825; 827-829
SQL, 102; 240: 244; 249; 255; 259; 261; 262;
264; 266; 267; 270; 272; 274-276; 278;
281; 282; 295; 297; 312; 319; 326; 328;
380; 395; 476; 532; 546; 547; 573; 582;
583; 606; 624; 625; 636; 639-641; 646;
652; 653; 687-689; 736; 738; 797; 798;
863
SSI, См. Серверное включение, 156
STD0053, 432
т
Thawte, 360; 368
V
VeriSign, 360; 368
w
Web-сервер, 37; 40; 41; 83; 86; 156; 157;
160; 163; 164; 235; 236; 238; 248; 284;
289; 308; 341; 352; 354; 355; 363; 368;
369; 381; 382; 386; 388; 394; 396-399;
402-407; 410-412: 414; 416; 417; 420;
424; 428; 430; 432; 466; 507; 532; 621;
648; 650; 684; 767; 838-840; 857-860
Web-форум, 509; 513; 732; 733; 736; 759
X
XML, 41; 42; 509: 623; 764; 769; 794-803;
810; 817; 819; 820: 822-824; 829; 835
z
Zend Technologies, 523
продукт
Zend Accelerator. 523
Zend Encoder, 523
Zend Optimizer, 523
Zend Studio, 519: 523
A
Атомарность, 326
Аудит, 349: 352; 369
Аутентификация, 354; 360; 362; 363; 374;
382; 384; 545
базовая, 382; 393
Б
Базовая линия, 474
Библиотека
GD2, 459; 460; 472: 483
ImageChar(). 465
ImageColorAllocate(), 464
ImageCreateFromGIF(), 464; 471
ImageCreateFromJPEGO, 464; 471
ImageCreateFromPNG(), 464; 471
ImageCreateTrueColor(), 464
ImageDestrov(), 467; 482
lmageFill(), 465
ImageFilledRectangleO, 480; 482
ImageJPEGO, 467
ImageLine(), 481
ImagePNG(), 467; 471; 482
ImageRectangle(, 480; 481; 482
ImageStringO, 465
ImageTTFText(), 473; 475; 482
ImageMagick, 459
mysqli, 285; 286; 288-296; 298; 299; 333;
378; 379; 457; 478; 494; 496; 515; 531;
532; 556; 606; 641; 844; 858
866
Предметный указатель
NuSOAP, 795; 796; 803; 807; 834
PCRE, 145
PECL, 459; 856: 861
SimpleXML, 795; 803
Брандмауэр, 370
В
Взломщик, 348; 349; 353; 356
Виртуальный хостинг, 439
Выделение цветом синтаксиса, 505
highlight_file(), 506
highlight_string(), 506
д
Двумерный файл, 81; 102; 110
Деструктор,186; 207
Дешифрация, 364
Директива конфигурации
allow_url_fopen, 86
auto_append_file, 163: 381
auto_prepend_file, 163; 381
display_errors, 538; 844
error_reporting, 538; 539; 819; 820
extension_dir, 504: 857
log_errors, 538
magic_quotes_gpc, 137; 285; 287; 288;
293; 294; 298; 402; 500; 583
magic_quotes_runtime, 402; 500
max connections, 289; 303
max_execution_time, 504; 505
MaxClients, 289
register_long_arrays, 46
track_errors, 62; 538; 539
upload_max_filesize, 417; 419
И
Идентификатор, 37; 50; 254; 255
Изоляция, 326
Инкапсуляция, 182; 187
Интерфейс, 196
К
Каскадная таблица стилей, 160
Класс
Exception, 214-220; 222; 552; 553; 556;
557; 559; 562—565; 567—569; 572; 574;
575
getCode(), 215—220
getFile(), 215-220
getLine(), 215—220
getMessage(), 215-220; 298; 299; 553;
562; 569; 574
getTrace(), 216; 217
getTraceAsStringO, 216; 218
mysqli, 285; 286; 288-296; 298; 299; 333;
378; 379; 457; 478; 494; 496; 515; 531;
532; 556; 606; 641; 844; 858
close, 90; 286; 292; 295; 332; 334; 422;
428; 494; 496; 650; 657; 658; 672; 675;
677; 680; 782; 785; 788; 789; 792
fetch_array(), 667; 702; 707; 709; 710;
717; 727; 730; 756; 758; 759
fetch_assoc(), 286; 291; 589; 627; 629;
635; 643; 645; 664; 709; 746; 749; 753
fetch_object(), 291; 478; 481; 567; 575;
591; 599; 605; 679
fetch_row(), 291; 570; 728; 743
query(), 295
Ключевое слово
abstract, 208
clone. 207; 208
final, 194; 195; 217; 218
global, 54: 172; 173; 174; 560; 670; 705;
719; 744; 814; 831-833
instanceof, 63; 206; 207; 222
private, 189; 191; 192; 204; 210; 217; 218;
365; 406; 409; 412; 817; 824; 825
protected, 189; 191; 192; 217; 388; 389
public, 86; 87; 189; 190: 192: 198-202;
208-210; 212; 366; 406; 527-530; 745;
747; 748
return, 175
Команда
DESCRIBE, 253; 309; 310; 312
Предметный указатель
867
GRANT, 243-248; 301; 303; 306; 308; 310;
311; 322
REVOKE, 243; 246; 247; 306
SHOW, 246; 253; 309-312; 322
Константа
E_ALL, 537; 538; 819; 820
E_COMPILE_ERROR, 537; 538
E_COMPILE_WARNING, 537; 538
E_CORE_ERROR, 537; 538
E_CORE_WARNING, 537; 538
E ERROR, 537; 538
E_NOTICE, 537; 538; 819; 820
E_PARSE, 537; 538
E_STRICT, 537; 538
E_USER_ERROR, 537; 538; 540-542
E_USER_NOTICE, 537; 538; 540; 541
E_USER_WARNING, 537; 538; 540; 541
E_WARNING, 537; 538
UPLOAD_ERR_FORM_SIZE, 419
UPLOAD_ERR_INI_SIZE, 419
UPLOAD_ERR_NO_FILE, 419
UPLOAD ERR PARTIAL, 419
UPLOAD_ERROR__OK, 418
Конструктор, 185; 186; 207; 211; 215; 216;
745; 748; 749; 824; 826
Конфигурация серансов
session.auto_start, 487; 492
session.cache_expire, 492
session. cookie_domain, 492
session.cookie_lifetime, 492
session.cookie_path, 492
session.name, 492
Session.save_handler, 492
session.save_path, 492
session.use_cookies, 492
Курсор, 331
M
Магические кавычки, 137; 138; 499; 500;
547; 583
Маршрутизатор, 358; 370
Массив, 47; 49; 51; 54; 55; 62; 63; 73; 95:96;
102-127; 135; 138:139; 144; 145; 151;
152; 165; 169-171; 198: 203; 208; 209:
211; 216; 291; 299; 333; 356; 371; 416;
417; 422; 425: 426: 428; 438; 439; 452;
453; 471: 473; 484; 486-489; 502; 503;
536; 554; 570; 575; 588; 589; 592; 593;
600; 615; 616; 628; 636; 639; 644; 660;
664; 667; 672; 675; 706-711; 720; 724;
735; 742-744: 749; 752; 753; 815; 817;
820; 828; 831
array_count_values(), 125
array_pop(). 120
array_push(). 120: 708
array_reverse(), 117; 119; 120
array_walk(), 124; 125
arsort(), 115
asort(), 114; 115; 117
compare(), 116
count (), 121; 125
current(), 123; 124; 210; 211
each(), 107; 108; 109; 112; 123; 124
end(), 123; 124
explode(), 122; 138; 139; 142; 439
extract (). 126; 127
krsort(), 115
ksort(), 114; 115; 117
list(), 107; 108; 109: 112: 667-671; 673;
716:717
next(), 123; 124; 210; 211
pos(), 123; 124
prev(), 123; 124
reset(), 109; 123; 124
rsort(), 115; 119
shuffleO, 117-119
sizeof(), 125
sort(), 104; 114; 115; 117
usort(), 115-117; 124
Механизм хранения
BDB, 312; 324
HEAP, 324
InnoDB, 279; 280; 312; 321; 324-328; 578:
583-585
ISAM, 324
MEMORY, 324; 325
MERGE, 324; 325
MylSAM, 254; 281; 321; 324; 325; 327
868
Предметный указатель
Многократное использование кода, 35;
154; 512
Модуль
mod_auth, 373; 385; 390-393
mod_auth_mysql, 373; 390—393
н
Наследование, 184; 190; 194—197; 203—205
Нижний выносной элемент, 474
О
Область действия, 37; 47; 54; 154; 171—
174; 178; 180; 302
глобальная, 172
локальная, 172
суперглобальная, 54; 172
Объект
mysql i
mysqli_close(), 292
mysqli_connect(), 288; 289; 515; 531
mysqli_connect_errno(), 286; 289; 294;
494; 496
mysqli_fetch_array(), 291
mysqli_fetch_assoc(), 290; 291
mysqli_fetch_object(), 291
mysqli_fetch_row(), 291; 333
mysqli_free_result(), 291
mysqli_num_rows(), 290; 295
mysqli_query(), 290; 295; 333; 531
mysqli_select_db(). 289
mysqli_stmt_bind_param(), 296
mysqli_stmt_bind_result(), 296
mysqli_stmt_execute(), 296
mysqli_stmt_fetch(), 296
mysqli__stmt_prepare(), 296
Ограничивающий прямоугольник, 473
Оператор
ALTER TABLE, 278-280; 317; 325; 326;
328
create procedure. 329: 331; 332
DELETE, 245; 247; 280; 281; 295; 310; 641
delimiter, 94; 329-332
die, 499; 501; 811
DROP DATABASE, 281
DROP TABLE, 281
echo, 39-44; 48-50; 54; 58; 62-64; 66-
70: 72: 73; 75-79; 87; 91-93; 96-98;
105-109; 111-113; 118; 120-122; 124
exit, 78; 79; 87; 92; 141; 152; 285-287;
289; 293; 294; 298; 299; 378; 379; 417;
418; 433; 437; 438; 440; 441; 443; 445;
469; 478; 494; 496; 499; 501; 541; 553;
557; 560; 571; 611; 629; 630; 641; 721-
723; 786
EXPLAIN, 309; 312; 314-318; 523
include(), 154: 156; 162; 163; 168; 171;
172; 180; 395
include_once(), 163
INSERT, 245; 247; 262; 263; 265; 278; 292;
295; 309; 310; 318; 325; 640
LOAD DATA INFILE, 323; 335
LOCK TABLES, 246; 318
OPTIMIZE TABLE, 318
require(), 154; 156—158; 160—163; 168;
171; 172; 180; 395
require_once(), 163; 803
SELECT. 245; 247; 264: 272; 274-276;
287: 292; 295: 296: 303; 308-310; 312;
314; 320; 325; 329; 454; 455; 612
UPDATE, 245; 247; 263; 278; 295; 310;
640
Операционная система
Linux, 231; 234; 264; 346; 354; 405; 407;
408; 460; 519; 520; 649; 839; 840; 843
Windows XP, 388; 838; 851: 857
Отказ
в обслуживании, 357
распределенный, 357
от обязательств, 360; 367; 397
Открытый исходный код, 405; 518; 519;
523; 577; 618; 760; 840; 863
п
Перекрытие, 193
Переменная
DOCUMENT_ROOT, 83-85; 87; 88; 90;
92; 93; 95; 97; 100; 120; 121; 220; 221
Предметный указатель
869
Полномочия
для администраторов, 246
для пользователей, 245
специальные, 246
Постоянство, 326
Проверка
is_array(), 67
is_callable(), 67
is_dir(), 426
is_double(), 67
is_executable(), 426
is_file(), 426
is_float(), 67
is_int(), 67
is_integer(), 67
is_link(), 426
is_long(), 67
is_null(), 67
is_numeric(), 67
is_object(), 67
is_readable(), 426
is_real(), 67
is_resource(), 67
is_scalar(), 67
is_string(), 67
is__uploaded_file(), 419
is_writable(), 426
Протокол, 399; 431
FTP, 85; 86; 89; 382; 399; 406; 414; 431;
432; 434; 439-447; 617; 621; 649; 769
ftp_connect(), 443
ftp_fget(), 445; 446
ftp_fput(), 446
ftp_get(), 446
ftp_mdtm(), 444
ftp_put(), 446
ftp_quit(), 443; 446
ftp_size(), 447
HTTP, 45-48; 83; 85; 86; 89; 95; 236; 284;
381-384; 386; 388; 393; 399; 414; 430-
432; 434; 439; 466; 484-486; 488; 491;
502; 535; 621; 633; 635; 637; 641; 777;
793; 795-797; 803; 810; 817; 819; 820;
822; 823; 847; 856; 859
IMAP, 399; 411; 432; 515; 647-650; 653;
671-673; 675; 677; 681; 841
NNTP, 432: 650; 681
POP, 399; 411; 432
SCP, 414; 621
S-HTTP, 397
SMTP, 399; 411; 432; 649; 650
SSL. 86: 244; 309; 345; 355; 361; 368; 382;
391-394; 396-401; 409; 411; 412; 595;
596; 601; 838; 840; 845-849; 856
TCP/IP, 355; 398; 399; 448; 854; 856
Telnet, 382; 390; 399: 442
P
Расширение
SQLite, 102
Регулярное выражение, 35; 129; 145; 146;
150; 153; 267; 522; 781
ereg(), 151
ereg_replace(), 152
eregi_replace(), 152
split(), 152; 644
Режим файла, 82
Репликация, 246; 319
С
Сеанс
serialize(), 502; 830
session_destroy(), 489-491; 497; 560; 608;
655; 691; 692; 698; 701; 714
session_get_cookie_params(). 486
session_register(), 488
session_set_cookie_params(), 486
session_start(), 487—490: 494—497; 503;
552; 557; 560; 562; 568; 571; 574; 587;
590; 591; 594; 602; 603; 608; 611; 612;
654; 659; 690; 720; 741; 808
session_unregister(), 488; 491
session_unset(), 488
Серверное включение, 156
Сериализация, 499; 502
serialize(), 502; 830
unserialize(), 502; 503; 830
870
Предметный указатель
Синтаксис heredoc, 50
Система управления реляционными
базами данных, 840
Служба имен доменов, 438
Создание экземпляра, 186
Соответствие ACID, 325
Спам,358
Стиль
POSIX, 145; 147; 150; 267
Стиль переменной
длинный, 46
короткий, 46
средний, 46; 47
Строка
str_replace(), 144; 145: 152; 777; 779; 781
strcasecmpO, 141
strchr(), 142
strcmpO, 141; 142
strip_tags(), 166; 402; 419; 487; 529
stripslashes(), 136; 137; 166; 287; 291; 309;
402; 500; 502; 529; 547
stristr(), 142; 143
strlen(), 89; 141; 179
strnatcmpO, 141
strpos(), 143; 144; 202
strrchr(), 142; 143
strrpos(), 143; 144
strstrO, 142; 143; 146; 530; 569
strtok(), 139; 142
strtolower(), 136
strtouppper(), 136
strval(), 68
substr(), 140
substr_replace(), 144; 145
T
Таблица
columns_priv, 305
db, 302; 304
host, 302; 304
tables_priv, 305
user, 302; 547
Тип данных
Array, 51; 356; 371; 536
Boolean, 51
Float, 51
Integer, 51
NULL, 51; 66; 136; 217; 250-253; 259;
260; 262-264; 266; 270; 272: 313; 315;
317; 500; 639; 665; 707; 717; 721; 758;
759
Object, 51; 210; 211; 796
pdfdoc, 52
pdfinfo, 52
resource, 51; 66; 86; 89; 94—96: 98; 99;
165; 289; 465; 862
String, 51
Транзакция,323;325
У
Управление версиями, 510; 518
Утилита
htpasswd, 387
phpautodoc, 520
phpdoc, 520
PHPDocumentator, 520
Ф
Файл
.htaccess
Auth_MySQLJ)B, 392
Auth_MySQL_Encryption_Types, 392
Auth_MySQL_Password_Field, 392
Auth_MySQL_Password_Table, 392
Auth_MySQL_Username_Field, 392
AuthGroupFile, 385; 386
AuthName, 385; 386; 392
AuthType, 385; 387; 392
AuthUserFile, 385; 386
ErrorDocument, 385; 386; 392
httpd.conf, 289; 388; 391; 392; 505; 847;
849; 856; 858
my.cnf, 320; 843
my.conf, 289
my.ini, 320; 851
Предметный указатель
871
php.ini, 45; 86; 163; 164; 222; 402; 419;
421; 430; 446; 487; 491; 500; 504-506;
538; 539; 650; 770; 844; 850; 857; 858
Файловая система
fclose(), 89; 90; 98; 422; 446
feof(), 93; 94; 96
fgetc(), 96
fgetcsv(), 94
fgets(). 94; 96; 566
fgetss(), 94
filed, 95; 96; 121; 506; 786; 790; 824
file_exists(), 97; 444
file_get_contents(), 89; 96; 434
filesize(), 97; 426
flock(), 99
fopen(), 82-89; 93; 95; 99; 165; 169; 222;
288; 422; 434; 439; 445; 530; 569
fpassthru(), 95
fputs(), 88
fread(), 96; 97
fseek(), 97; 98
ftell(), 97; 98
fwrite(), 88; 89
NFS, 99
readfile(), 95; 96; 162
rewind(), 97; 98; 210; 211; 422
unlink(), 97; 411; 427
Функция
adddate(), 456
addslashes(), 136; 137; 287; 288; 294; 309;
402; 500; 502; 515; 533; 547; 583
array_count__values (), 125
array_pop(), 120
array__push(), 120; 708
array_reverse(), 117; 119; 120
arraywalk(), 124; 125
arsort(), 115
asort(), 114; 115; 117
basenamed, 423—425; 641
checkdate(), 453
chgrp(), 426
chmod(), 426
chop(), 132
chown(), 426
clearstatcache(), 426
compared. H6
copy(),427
count(), 121; 125
current(), 123; 124; 210; 211
date(), 44; 45; 425; 449-452; 454; 457;
504; 527
DATE-FORMATd, 454
datediffd. 457
dirname(), 423: 425; 447; 640
disk__free__space(), 423
dl(),504
dns__get_mx(), 436; 437; 439
each(). 107-109: 112; 123; 124
emptyO, 68; 139; 488
end(), 123; 124
ereg(), 151
ereg_replace(), 152
eregi_replace(), 152
error_reporting(), 539
escapeshellcrndd, 402; 430
exec(), 402; 428
exploded, 122; 138; 139; 142: 439
extract(), 126; 127
EXTR_IF_EXISTS, 127
EXTRO VERWRITE, 127
EXTR_PREF_IF_EXISTS, 127
EXTR_PREFIX_ALL, 127
EXTR-PREFIX..INVALID, 127
EXTR_PREFIX_SAME, 127
EXTR_REFS, 127
EXTR-SKIP, 127
fclose(). 89; 90; 98; 422; 446
feof(), 93; 94; 96
fgetc(), 96
fgetcsvl), 94
fgets(), 94; 96; 566
fgetss(). 94
file(), 95; 96: 121; 506; 786; 790; 824
file_existsd. 97: 444
file__get__contents(), 89; 96; 434
fileatime(), 425
filegroupd, 424; 425
filemtime(), 425; 444
fileowner(), 424; 425
872
Предметный указатель
fileperms(), 426
filesize(), 97; 426
filetype(), 426
floatvalO, 68; 825; 826
flock(),99
floor(), 455
fopen(), 82-88; 89; 93; 95; 99; 165; 169;
222; 288; 422; 434; 439; 145; 530; 569
fpassthru(), 95
fputs(), 88
fread(), 96; 97
fseek(), 97; 98
ftell(),97; 98
ftp_connect(), 443
ftp_fget(), 445; 446
ftp_fput(), 446
ftP_get (), 446
ftp_mdtm(), 444
ftp_put(), 446
ftp_quit(), 443; 446
ftp_size(), 447
fwriteQ, 88; 89
gei_current_user(), 504
get_extension_funcs(), 503
get_loaded_extensions(), 503
getdate(), 452; 453
getenv(), 430
getlastmod(), 504
gettype(), 66; 67
Header() , 466
highlight_file(), 506
highlight_string(), 506
htmlspecialchars!), 288; 402
ImageChar(), 465
ImageColorAllocate!), 464
ImageCreateFromGIF!), 464; 471
ImageCreateFromJPEG(), 464; 471
ImageCreateFromPNG(), 464; 471
ImageCreateTrueColor(), 464
ImageDestroy!). 467; 482
ImageFill!), 465
ImageFilledRectangle!), 480; 482
ImageJPEG(), 467
ImageLine(), 481
ImagePNGO, 467; 471; 482
ImageRectangleO, 480; 481; 482
ImageString!), 465
ImageTTFTextO, 473; 475; 482
implode!), 138
ini_get(), 505
ini_set(), 505
PHP_INI_ALL, 505
PHP_INI_PERDIR, 505
PHP_INI_SYSTEM, 505
PHP_INI_USER, 505
intval(), 68; 121; 123; 481; 590; 629; 809;
812
is_array(), 67
is_callable(), 67
is_dir(), 426
is_double(), 67
is_execut able(), 426
is_file(), 426
is_float(), 67
is_int(), 67
is_integer(), 67
is_link(), 426
is_long(). 67
is_null(), 67
is_numeric(), 67
is_object(), 67
is_readable(), 426
is_real(), 67
is_resource(), 67
is_scalar(), 67
is_string(), 67
is_uploaded_file(), 419
is_writable(), 426
isset(), 67; 68; 131; 176; 488
join(), 138
krsort(), 115
ksort(), 114; 115; 117
list(), 107-109; 112; 667-671; 673; 716;
717
lstat(), 426
ltrim(), 132
mail(), 131; 432; 567; 649; 650: 679; 685;
729; 731; 732: 772; 841
Предметный указатель
873
md5(),380
microtime(), 457; 564; 565
mkdir(), 423
mktime(), 451; 452; 455; 457; 829
move_uploaded_file(), 419
mysqli_close(), 292
mysqli_connect(), 288; 289; 515; 531
mysqli_connect_errno(), 286; 289; 294;
494; 496
mysqli_fetch_array(), 291
mysqli_fetch_assoc(), 290; 291
mysqli_fetch_object(), 291
mysqli_fetch_row(), 291; 333
mysqli_free_result(), 291
mysqli_num_rows(), 290; 295
mysqlLqueryO, 290; 295; 333; 531
mysqli_select_db(), 289
mysqli_stmt_bind_param(), 296
mysqli_stmt_bind_result(), 296
mysqli_stmt_execute(), 296
mysqli_stmt_fetch(), 296
mysqli_stmt_prepare(), 296
next(), 123; 124; 210; 211
n!2br(), 97; 132; 133
now(), 457; 728; 758
number_format(), 64; 91; 221; 457; 597;
773; 831
parse_url(), 438
passthru(), 428
phpinfo(), 54; 164; 430; 649; 650; 770;
838; 849; 859
pos(), 123; 124
posix_getgrgid(), 425
posix_getpwuid(), 424; 425
prev(), 123; 124
print(), 133; 523
print_r(), 535; 536
printf(), 133; 134; 296
putenv(), 430
readfile(), 95; 96; 162
rename(), 427
reset(), 109:123; 124
rewind(), 97; 98; 210; 211; 422
rmdir(),423
rsort(), 115; 119
serialize(), 502; 830
session_destroy(), 489—491; 497; 560; 608;
655; 691; 692; 698; 701; 714
session_get_cookie_params(), 486
session_register(), 488
session_set_cookie_params(). 486
session_start(), 487—490; 494—497; 503;
552; 557; 560; 562; 568; 571; 574; 587;
590; 591; 594; 602; 603; 608; 611; 612;
654; 659; 690; 720; 741; 808
session_unregister(), 488; 491
session_unset (), 488
set_error_handler(), 540; 541
set_time_limit(), 446; 447
settype(), 66; 67
shal(), 380
shuffle(), 117-119
sizeof(), 125
sort(), 104; 114; 115; 117
split (), 152; 644
sprintf(), 133
stat(), 426
str_replace(), 144; 145; 152; 777; 779; 781
strcasecmpO, 141
strchr(), 142
strcmpO, 141; 142
strip_tags(), 166; 402; 419; 487; 529
stripslashes(), 136; 137; 166; 287; 291; 309;
402; 500; 502; 529; 547
stristr(), 142; 143
strlen(),89; 141; 179
strnatcmpO, 141
strpos(), 143: 144; 202
strrchr(), 142; 143
strrpos(), 143; 144
strstr(), 142; 143; 146; 530; 569
strtok(), 139; 142
strtolower(), 136
strtouppper(), 136
strval(), 68
substr(), 140
substr_replace(), 144; 145
system(), 402; 410; 411; 427-430
time(), 452; 455; 457; 640
874
Предметный указатель
touch(), 427
trigger_error(), 540
trim(), 132; 287
ucfirst(), 136
ucwords(), 136
umask(), 423
UNIX_TIMESTAMP(), 454
unlink(), 97; 411; 427
unserialize(), 502; 503; 830
unset(), 68; 601
usort(), 115—117; 124
доступа, 187; 188; 189; 824
объявление, 79; 167; 331—333
перегрузка, 168
передача по значению, 154; 174
передача по ссылке, 154; 174
рекурсивная, 178
X
Хеш-функция, 367
MD5, 367; 380; 382
SHA, 367; 380
Хранимая процедура, 323; 328; 334
create procedure, 329; 331; 332
delimiter, 94; 329; 330; 331; 332
ц
Целостность, 232; 326
Центр сертификации, 360; 368
Thawte, 360; 368
VeriSign, 360; 368
Цифровая подпись, 352: 366
III
Шифрование, 352; 361; 364—367; 387;
392-394; 400; 401; 404; 406: 411
с закрытым ключом, 352; 365
с открытым ключом, 352; 366
э
Электронная коммерция, 36; 337; 338;
342; 343; 347-353; 360; 368; 374; 412;
838
Предметный указатель
875
Научно-популярное издание
Люк Веллинг, Лора Томсон
Разработка Web-приложений
с помощью РНР и MySQL
3-е издание
Верстка Т.Н. Артеменко
Художественный редактор С.А. Чернокозинский
Издательский дом “Вильямс”
127055, г. Москва, ул. Лесная, д. 43, стр. 1
Подписано в печать 15.01.2008. Формат 70x100, 16.
Гарнитура Times. Печать офсетная.
Усл. печ. л. 70,95. Уч.-изд. л. 45,4.
Доп. тираж 1000 экз. Заказ № 6663.
Отпечатано по технологии CtP
в ОАО “Печатный двор” им. А М. Горького
197110, Санкт-Петербург, Чкаловский пр., 15.